Skip to content

Commit

Permalink
create services for uniut
Browse files Browse the repository at this point in the history
  • Loading branch information
kcinay055679 committed Jan 21, 2025
1 parent f7bb13e commit 4a03ccb
Show file tree
Hide file tree
Showing 12 changed files with 279 additions and 9 deletions.
2 changes: 1 addition & 1 deletion backend/src/main/java/ch/puzzle/okr/ErrorKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ public enum ErrorKey {
TOKEN_NULL,
TRIED_TO_DELETE_LAST_ADMIN,
TRIED_TO_REMOVE_LAST_OKR_CHAMPION,
UNIT_NOT_FOUND
MODEL_NOT_FOUND_BY_PROPERTY
}
5 changes: 4 additions & 1 deletion backend/src/main/java/ch/puzzle/okr/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import static org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter.ReferrerPolicy.NO_REFERRER;
import static org.springframework.security.web.header.writers.XXssProtectionHeaderWriter.HeaderValue.ENABLED_MODE_BLOCK;

import ch.puzzle.okr.models.User;
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
Expand All @@ -16,6 +17,8 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
Expand All @@ -40,6 +43,7 @@
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@EnableJpaAuditing
public class SecurityConfig {
private static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);

Expand Down Expand Up @@ -126,5 +130,4 @@ private String okrPermissionPolicy() {
public AuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
return new DefaultAuthenticationEventPublisher(applicationEventPublisher);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package ch.puzzle.okr;

import ch.puzzle.okr.models.User;
import ch.puzzle.okr.security.JwtHelper;
import ch.puzzle.okr.service.authorization.AuthorizationService;
import org.springframework.data.domain.AuditorAware;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.stereotype.Component;

import java.util.Optional;

@Component
class SpringSecurityAuditorAware implements AuditorAware<User> {

private final JwtHelper jwtHelper;
private final AuthorizationService authorizationService;

public SpringSecurityAuditorAware(JwtHelper jwtHelper, AuthorizationService authorizationService) {
this.jwtHelper = jwtHelper;
this.authorizationService = authorizationService;
}

@Override
public Optional<User> getCurrentAuditor() {
return Optional
.ofNullable(SecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication)
.filter(Authentication::isAuthenticated)
.map(Authentication::getPrincipal)
.map(e -> (Jwt) e)
.map(jwtHelper::getUserFromJwt)
.map(e -> authorizationService.updateOrAddAuthorizationUser().user());
}
}
58 changes: 58 additions & 0 deletions backend/src/main/java/ch/puzzle/okr/controller/UnitController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package ch.puzzle.okr.controller;

import ch.puzzle.okr.dto.ActionDto;
import ch.puzzle.okr.dto.UnitDto;
import ch.puzzle.okr.mapper.UnitMapper;
import ch.puzzle.okr.models.Unit;
import ch.puzzle.okr.service.authorization.UnitAuthorizationService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("api/v2/unit")
public class UnitController {
private final UnitAuthorizationService unitAuthorizationService;
private final UnitMapper unitMapper;

public UnitController(UnitAuthorizationService unitAuthorizationService, UnitMapper unitMapper) {
this.unitAuthorizationService = unitAuthorizationService;
this.unitMapper = unitMapper;
}

@Operation(summary = "Update Actions", description = "Update Actions of KeyResult")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Updated Actions of KeyResult", content = {
@Content(mediaType = "application/json", schema = @Schema(implementation = ActionDto.class)) }),
@ApiResponse(responseCode = "400", description = "Can't update Actions, attributes are not set", content = @Content) })
@PostMapping
public UnitDto createUnit(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "The Action as json to update existing Actions.", required = true)
@RequestBody UnitDto unitDto) {
Unit unit = unitMapper.toUnit(unitDto);
return unitMapper.toDto(unitAuthorizationService.createUnit(unit));
}


// @Operation(summary = "Update Actions", description = "Update Actions of KeyResult")
// @ApiResponses(value = {
// @ApiResponse(responseCode = "200", description = "Updated Actions of KeyResult", content = {
// @Content(mediaType = "application/json", schema = @Schema(implementation = ActionDto.class)) }),
// @ApiResponse(responseCode = "400", description = "Can't update Actions, attributes are not set", content = @Content) })
// @PutMapping
// public void updateUnit(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "The Action as json to update existing Actions.", required = true)
// @RequestBody List<ActionDto> actionDtoList) {
// List<Action> actionList = unitMapper.toActions(actionDtoList);
// unitAuthorizationService.updateEntities(actionList);
// }
//
// @Operation(summary = "Delete Action by Id", description = "Delete Action by Id")
// @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Deleted Action by Id"),
// @ApiResponse(responseCode = "404", description = "Did not find the Action with requested id") })
// @DeleteMapping("/{unitId}")
// public void deleteUnitById(@PathVariable long unitId) {
// unitAuthorizationService.deleteActionByActionId(actionId);
// }
}
4 changes: 4 additions & 0 deletions backend/src/main/java/ch/puzzle/okr/dto/UnitDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package ch.puzzle.okr.dto;

public record UnitDto(Long id, String unitName, UserDto owner) {
}
37 changes: 37 additions & 0 deletions backend/src/main/java/ch/puzzle/okr/mapper/UnitMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package ch.puzzle.okr.mapper;

import ch.puzzle.okr.dto.ObjectiveDto;
import ch.puzzle.okr.dto.UnitDto;
import ch.puzzle.okr.dto.UserDto;
import ch.puzzle.okr.models.Objective;
import ch.puzzle.okr.models.Unit;
import ch.puzzle.okr.service.business.QuarterBusinessService;
import ch.puzzle.okr.service.business.TeamBusinessService;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.List;

@Component
public class UnitMapper {

private final UserMapper userMapper;

public UnitMapper(UserMapper userMapper) {
this.userMapper = userMapper;
}

public UnitDto toDto(Unit unit) {
return new UnitDto(unit.getId(),
unit.getUnitName(),
userMapper.toDto(unit.getCreatedBy()));
}

public Unit toUnit(UnitDto objectiveDto) {
return Unit.Builder
.builder()
.id(objectiveDto.id())
.unitName(objectiveDto.unitName())
.build();
}
}
9 changes: 7 additions & 2 deletions backend/src/main/java/ch/puzzle/okr/models/Unit.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import java.util.Objects;

@Entity(name = "unit")
@EntityListeners(AuditingEntityListener.class)
public class Unit {
@Id
@GeneratedValue(strategy = GenerationType.AUTO, generator = "sequence_unit")
Expand All @@ -32,8 +36,9 @@ public int hashCode() {
@Size(max = 4096, min = 3, message = MessageKey.ATTRIBUTE_SIZE_BETWEEN)
private String unitName;

@NotNull(message = MessageKey.ATTRIBUTE_NOT_NULL)
@ManyToOne
@CreatedBy
// @NotNull(message = MessageKey.ATTRIBUTE_NOT_NULL)
@ManyToOne(cascade = CascadeType.ALL)
private User createdBy;

public Long getId() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package ch.puzzle.okr.service.authorization;

import ch.puzzle.okr.Constants;
import ch.puzzle.okr.ErrorKey;
import ch.puzzle.okr.exception.OkrResponseStatusException;
import ch.puzzle.okr.models.Unit;
import ch.puzzle.okr.models.authorization.AuthorizationUser;
import ch.puzzle.okr.service.business.UnitBusinessService;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;

@Service
public class UnitAuthorizationService {
private final UnitBusinessService unitBusinessService;
private final AuthorizationService authorizationService;

public UnitAuthorizationService(UnitBusinessService unitBusinessService,
AuthorizationService authorizationService) {
this.unitBusinessService = unitBusinessService;
this.authorizationService = authorizationService;
}

public Unit createUnit(Unit unit) {
return unitBusinessService.createEntity(unit);
}

public Unit editUnit(Unit unit) {
AuthorizationUser authorizationUser = authorizationService.updateOrAddAuthorizationUser();
if(!isOwner(unit, authorizationUser)) {
throw new OkrResponseStatusException(HttpStatus.FORBIDDEN, ErrorKey.NOT_AUTHORIZED_TO_WRITE, Constants.UNIT);
}
return unitBusinessService.updateEntity(unit.getId(), unit);
}

private boolean isOwner(Unit unit, AuthorizationUser authorizationUser) {
return unitBusinessService.getEntityById(unit.getId()).getCreatedBy().getId().equals(authorizationUser.user().getId());
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ch.puzzle.okr.service.business;

import ch.puzzle.okr.Constants;
import ch.puzzle.okr.ErrorKey;
import ch.puzzle.okr.exception.OkrResponseStatusException;
import ch.puzzle.okr.models.Unit;
Expand All @@ -8,6 +9,8 @@
import jakarta.transaction.Transactional;
import java.util.ArrayList;
import java.util.List;

import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;

@Service
Expand All @@ -23,7 +26,8 @@ public UnitBusinessService(UnitPersistenceService unitPersistenceService, UnitVa
public Unit findUnitByName(String unitName) {
return unitPersistenceService
.findUnitByUnitName(unitName)
.orElseThrow(() -> OkrResponseStatusException.of(ErrorKey.UNIT_NOT_FOUND, unitName));
.orElseThrow( ()-> new OkrResponseStatusException(HttpStatus.NOT_FOUND, ErrorKey.MODEL_NOT_FOUND_BY_PROPERTY, List.of(
Constants.UNIT, "unit name", unitName)));
}

public Unit getEntityById(Long id) {
Expand All @@ -45,9 +49,11 @@ public List<Unit> updateEntities(List<Unit> actionList) {
}

@Transactional
public Unit updateEntity(Long id, Unit action) {
validator.validateOnUpdate(id, action);
return unitPersistenceService.save(action);
public Unit updateEntity(Long id, Unit newUnit) {
validator.validateOnUpdate(id, newUnit);
Unit oldUnit = unitPersistenceService.findById(id);
oldUnit.setUnitName(newUnit.getUnitName());
return unitPersistenceService.save(oldUnit);
}

@Transactional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ public void validateOnGetByKeyResultId(Long keyResultId) {
@Override
public void validateOnCreate(Unit model) {
throwExceptionWhenModelIsNull(model);
throwExceptionWhenIdIsNotNull(model.getId());

validate(model);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package ch.puzzle.okr.controller;

import ch.puzzle.okr.multitenancy.TenantConfigProvider;
import ch.puzzle.okr.multitenancy.TenantContext;
import ch.puzzle.okr.service.persistence.UnitPersistenceService;
import ch.puzzle.okr.test.SpringIntegrationTest;
import ch.puzzle.okr.test.TestHelper;
import com.nimbusds.jwt.JWT;
import jdk.jfr.ContentType;
import org.hamcrest.Matchers;
import org.hamcrest.core.Is;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;

@WithMockUser
@AutoConfigureMockMvc
@SpringIntegrationTest
class UnitControllerIT {
@Autowired
private MockMvc mvc;
private final String URL_BASE = "/api/v2/unit";
String CREATE_UNIT_BODY = "{\"unitName\":\"TestUnit\"}";

@Autowired
private UnitPersistenceService unitPersistenceService;

@Test
void test() throws Exception {
Jwt jwt = TestHelper.mockJwtToken("Jaya", "Norris", "[email protected]");

TenantContext.setCurrentTenant("pitc");
mvc
.perform(post(URL_BASE).with(SecurityMockMvcRequestPostProcessors.csrf()). with(SecurityMockMvcRequestPostProcessors.jwt().jwt(jwt))
.content(CREATE_UNIT_BODY).contentType("application/json"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(jsonPath("$.owner.email", Is.is("[email protected]")));
}
}
28 changes: 28 additions & 0 deletions backend/src/test/java/ch/puzzle/okr/test/TestHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.nimbusds.jwt.JWT;
import org.springframework.security.oauth2.jwt.Jwt;

public class TestHelper {
Expand All @@ -39,6 +41,16 @@ public static User defaultUser(Long id) {
.build();
}

public static Jwt getUserJwt(){
Map<String, Object> claims = new HashMap<>();
claims.put("name", FIRST_NAME + " " + LAST_NAME);
claims.put("preferred_username", FIRST_NAME);
claims.put("given_name", FIRST_NAME);
claims.put("family_name", LAST_NAME);
claims.put("email", EMAIL);
return generateJWT(claims);
}

public static User defaultOkrChampion(Long id) {
var user = defaultUser(id);
user.setOkrChampion(true);
Expand Down Expand Up @@ -134,4 +146,20 @@ public static JsonNode getJsonNode(String json) throws IOException {
public static final Unit EUR_UNIT = Unit.Builder.builder().unitName("EUR").build();
public static final Unit PERCENT_UNIT = Unit.Builder.builder().unitName("PERCENT").build();

private static Jwt generateJWT(Map<String, Object> claims) {
final String AUTH0_TOKEN = "token";
final String SUB = "sub";
final String AUTH0ID = "sms|12345678";
// This is a place to add general and maybe custom claims which should be available after parsing token in the live system

//This is an object that represents contents of jwt token after parsing
return new Jwt(
AUTH0_TOKEN,
Instant.now(),
Instant.now().plusSeconds(120),
Map.of("alg", "RS256", "typ", "JWT"),
claims
);
}

}

0 comments on commit 4a03ccb

Please sign in to comment.