Skip to content

Commit b997c46

Browse files
Merge pull request #937 from exadel-inc/EFRS-1316_remove_special_characters_from_names
EFRS-1316: Added a custom liquibase change in order to remove special characters from names
2 parents fea35bf + b2b4104 commit b997c46

File tree

20 files changed

+350
-40
lines changed

20 files changed

+350
-40
lines changed

java/admin/src/main/java/com/exadel/frs/dto/ui/AppCreateDto.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616

1717
package com.exadel.frs.dto.ui;
1818

19+
import static com.exadel.frs.commonservice.system.global.RegExConstants.ALLOWED_SPECIAL_CHARACTERS;
1920
import javax.validation.constraints.NotBlank;
21+
import javax.validation.constraints.Pattern;
2022
import javax.validation.constraints.Size;
2123
import lombok.AllArgsConstructor;
2224
import lombok.Builder;
@@ -31,5 +33,6 @@ public class AppCreateDto {
3133

3234
@NotBlank(message = "Application name cannot be empty")
3335
@Size(min = 1, max = 50, message = "Application name size must be between 1 and 50")
36+
@Pattern(regexp = ALLOWED_SPECIAL_CHARACTERS, message = "The name cannot contain the following special characters: ';', '/', '\\'")
3437
private String name;
3538
}

java/admin/src/main/java/com/exadel/frs/dto/ui/AppUpdateDto.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616

1717
package com.exadel.frs.dto.ui;
1818

19+
import static com.exadel.frs.commonservice.system.global.RegExConstants.ALLOWED_SPECIAL_CHARACTERS;
1920
import javax.validation.constraints.NotBlank;
21+
import javax.validation.constraints.Pattern;
2022
import javax.validation.constraints.Size;
2123
import lombok.AllArgsConstructor;
2224
import lombok.Builder;
@@ -31,5 +33,6 @@ public class AppUpdateDto {
3133

3234
@NotBlank(message = "Application name cannot be empty")
3335
@Size(min = 1, max = 50, message = "Application name size must be between 1 and 50")
36+
@Pattern(regexp = ALLOWED_SPECIAL_CHARACTERS, message = "The name cannot contain the following special characters: ';', '/', '\\'")
3437
private String name;
3538
}

java/admin/src/main/java/com/exadel/frs/dto/ui/ModelCloneDto.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616

1717
package com.exadel.frs.dto.ui;
1818

19+
import static com.exadel.frs.commonservice.system.global.RegExConstants.ALLOWED_SPECIAL_CHARACTERS;
1920
import javax.validation.constraints.NotBlank;
21+
import javax.validation.constraints.Pattern;
2022
import javax.validation.constraints.Size;
2123
import lombok.AllArgsConstructor;
2224
import lombok.Builder;
@@ -31,5 +33,6 @@ public class ModelCloneDto {
3133

3234
@NotBlank(message = "Model name cannot be empty")
3335
@Size(min = 1, max = 50, message = "Model name size must be between 1 and 50")
36+
@Pattern(regexp = ALLOWED_SPECIAL_CHARACTERS, message = "The name cannot contain the following special characters: ';', '/', '\\'")
3437
private String name;
3538
}

java/admin/src/main/java/com/exadel/frs/dto/ui/ModelCreateDto.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616

1717
package com.exadel.frs.dto.ui;
1818

19+
import static com.exadel.frs.commonservice.system.global.RegExConstants.ALLOWED_SPECIAL_CHARACTERS;
1920
import com.exadel.frs.commonservice.enums.ModelType;
2021
import com.exadel.frs.validation.ValidEnum;
2122
import javax.validation.constraints.NotBlank;
23+
import javax.validation.constraints.Pattern;
2224
import javax.validation.constraints.Size;
2325
import lombok.AllArgsConstructor;
2426
import lombok.Builder;
@@ -33,6 +35,7 @@ public class ModelCreateDto {
3335

3436
@NotBlank(message = "Model name cannot be empty")
3537
@Size(min = 1, max = 50, message = "Model name size must be between 1 and 50")
38+
@Pattern(regexp = ALLOWED_SPECIAL_CHARACTERS, message = "The name cannot contain the following special characters: ';', '/', '\\'")
3639
private String name;
3740

3841
@NotBlank(message = "Model Type cannot be empty")

java/admin/src/main/java/com/exadel/frs/dto/ui/ModelUpdateDto.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616

1717
package com.exadel.frs.dto.ui;
1818

19+
import static com.exadel.frs.commonservice.system.global.RegExConstants.ALLOWED_SPECIAL_CHARACTERS;
1920
import javax.validation.constraints.NotBlank;
21+
import javax.validation.constraints.Pattern;
2022
import javax.validation.constraints.Size;
2123
import lombok.AllArgsConstructor;
2224
import lombok.Builder;
@@ -31,5 +33,6 @@ public class ModelUpdateDto {
3133

3234
@NotBlank(message = "Model name cannot be empty")
3335
@Size(min = 1, max = 50, message = "Model name size must be between 1 and 50")
36+
@Pattern(regexp = ALLOWED_SPECIAL_CHARACTERS, message = "The name cannot contain the following special characters: ';', '/', '\\'")
3437
private String name;
3538
}

java/admin/src/main/resources/application.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
server:
22
port: ${CRUD_PORT:8080}
3+
tomcat:
4+
relaxed-path-chars: [ '[', ']' ]
5+
relaxed-query-chars: [ '[', ']' ]
36

47
app:
58
security:
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
databaseChangeLog:
2+
- changeSet:
3+
id: remove-special-characters-from-names
4+
author: Volodymyr Bushko
5+
changes:
6+
- customChange: {
7+
"class": "com.exadel.frs.commonservice.system.liquibase.customchange.RemoveSpecialCharactersCustomChange",
8+
"table": "app",
9+
"primaryKeyColumn": "id",
10+
"targetColumn": "name"
11+
}
12+
- customChange: {
13+
"class": "com.exadel.frs.commonservice.system.liquibase.customchange.RemoveSpecialCharactersCustomChange",
14+
"table": "model",
15+
"primaryKeyColumn": "id",
16+
"targetColumn": "name"
17+
}
18+
- customChange: {
19+
"class": "com.exadel.frs.commonservice.system.liquibase.customchange.RemoveSpecialCharactersCustomChange",
20+
"table": "subject",
21+
"primaryKeyColumn": "id",
22+
"targetColumn": "subject_name"
23+
}

java/admin/src/main/resources/db/changelog/db.changelog-master.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,5 @@ databaseChangeLog:
4545
file: db/changelog/db.changelog-0.2.2.yaml
4646
- include:
4747
file: db/changelog/db.changelog-0.2.3.yaml
48+
- include:
49+
file: db/changelog/db.changelog-0.2.4.yaml

java/admin/src/test/java/com/exadel/frs/controller/AppControllerTest.java

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,14 @@
5555
import com.exadel.frs.system.security.config.AuthServerConfig;
5656
import com.exadel.frs.system.security.config.ResourceServerConfig;
5757
import com.exadel.frs.system.security.config.WebSecurityConfig;
58+
import com.fasterxml.jackson.core.JsonProcessingException;
5859
import com.fasterxml.jackson.databind.ObjectMapper;
5960
import java.util.List;
61+
import lombok.SneakyThrows;
6062
import lombok.val;
6163
import org.junit.jupiter.api.Test;
64+
import org.junit.jupiter.params.ParameterizedTest;
65+
import org.junit.jupiter.params.provider.ValueSource;
6266
import org.springframework.beans.factory.annotation.Autowired;
6367
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
6468
import org.springframework.boot.test.mock.mockito.MockBean;
@@ -163,7 +167,8 @@ public void shouldReturn400AndErrorMessageWhenRenameAppToEmpty() throws Exceptio
163167
.andExpect(content().string(expectedContent));
164168
}
165169

166-
@Test
170+
@ParameterizedTest
171+
@ValueSource(strings = {APP_NAME, "_[my_new app.]_"})
167172
public void shouldReturnNewApp() throws Exception {
168173
val appCreateDto = AppCreateDto.builder()
169174
.name(APP_NAME)
@@ -359,4 +364,26 @@ public void shouldReturnOkWhenDeleteUserFromApp() throws Exception {
359364
mockMvc.perform(request)
360365
.andExpect(status().isOk());
361366
}
362-
}
367+
368+
@Test
369+
@SneakyThrows
370+
public void shouldReturn400WhenTryingToSaveAppThatContainsSpecialCharactersWithinName() {
371+
var app = App.builder()
372+
.id(APP_ID)
373+
.name("\\new;app//")
374+
.build();
375+
376+
val request = post(ADMIN + "/app")
377+
.with(csrf())
378+
.with(user(buildUser()))
379+
.contentType(MediaType.APPLICATION_JSON)
380+
.content(mapper.writeValueAsString(app)
381+
);
382+
383+
val expectedContent = "{\"message\":\"The name cannot contain the following special characters: ';', '/', '\\\\'\",\"code\":36}";
384+
385+
mockMvc.perform(request)
386+
.andExpect(status().isBadRequest())
387+
.andExpect(content().string(expectedContent));
388+
}
389+
}

java/admin/src/test/java/com/exadel/frs/controller/ModelControllerTest.java

Lines changed: 59 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,23 @@
1616

1717
package com.exadel.frs.controller;
1818

19+
import static com.exadel.frs.system.global.Constants.ADMIN;
20+
import static com.exadel.frs.utils.TestUtils.buildUser;
21+
import static java.util.UUID.randomUUID;
22+
import static org.mockito.ArgumentMatchers.any;
23+
import static org.mockito.ArgumentMatchers.anyLong;
24+
import static org.mockito.ArgumentMatchers.eq;
25+
import static org.mockito.Mockito.doNothing;
26+
import static org.mockito.Mockito.when;
27+
import static org.springframework.http.MediaType.APPLICATION_JSON;
28+
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
29+
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
30+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
31+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
32+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
33+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
34+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
35+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
1936
import com.exadel.frs.commonservice.entity.Model;
2037
import com.exadel.frs.commonservice.repository.ModelStatisticRepository;
2138
import com.exadel.frs.dto.ui.ModelCreateDto;
@@ -27,30 +44,19 @@
2744
import com.exadel.frs.system.security.config.ResourceServerConfig;
2845
import com.exadel.frs.system.security.config.WebSecurityConfig;
2946
import com.fasterxml.jackson.databind.ObjectMapper;
47+
import java.util.List;
48+
import lombok.SneakyThrows;
3049
import lombok.val;
3150
import org.junit.jupiter.api.Test;
51+
import org.junit.jupiter.params.ParameterizedTest;
52+
import org.junit.jupiter.params.provider.ValueSource;
3253
import org.springframework.beans.factory.annotation.Autowired;
3354
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
3455
import org.springframework.boot.test.mock.mockito.MockBean;
3556
import org.springframework.context.annotation.ComponentScan;
3657
import org.springframework.context.annotation.FilterType;
3758
import org.springframework.test.web.servlet.MockMvc;
3859

39-
import java.util.List;
40-
41-
import static com.exadel.frs.system.global.Constants.ADMIN;
42-
import static com.exadel.frs.utils.TestUtils.buildUser;
43-
import static java.util.UUID.randomUUID;
44-
import static org.mockito.ArgumentMatchers.*;
45-
import static org.mockito.Mockito.doNothing;
46-
import static org.mockito.Mockito.when;
47-
import static org.springframework.http.MediaType.APPLICATION_JSON;
48-
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
49-
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
50-
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
51-
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
52-
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
53-
5460
@WebMvcTest(controllers = ModelController.class,
5561
excludeFilters = @ComponentScan.Filter(
5662
type = FilterType.ASSIGNABLE_TYPE,
@@ -92,12 +98,12 @@ void shouldReturnMessageAndCodeWhenModelNameIsMissingOnUpdate() throws Exception
9298
.contentType(APPLICATION_JSON);
9399

94100
mockMvc.perform(updateRequest.content(mapper.writeValueAsString(bodyWithEmptyName)))
95-
.andExpect(status().isBadRequest())
96-
.andExpect(content().string(expectedContent));
101+
.andExpect(status().isBadRequest())
102+
.andExpect(content().string(expectedContent));
97103

98104
mockMvc.perform(updateRequest.content(mapper.writeValueAsString(bodyWithNoName)))
99-
.andExpect(status().isBadRequest())
100-
.andExpect(content().string(expectedContent));
105+
.andExpect(status().isBadRequest())
106+
.andExpect(content().string(expectedContent));
101107
}
102108

103109
@Test
@@ -136,8 +142,8 @@ void shouldReturnModel() throws Exception {
136142
when(modelService.getModelDto(eq(APP_GUID), eq(MODEL_GUID), anyLong())).thenReturn(responseDto);
137143

138144
mockMvc.perform(request)
139-
.andExpect(status().isOk())
140-
.andExpect(content().string(mapper.writeValueAsString(responseDto)));
145+
.andExpect(status().isOk())
146+
.andExpect(content().string(mapper.writeValueAsString(responseDto)));
141147
}
142148

143149
@Test
@@ -153,11 +159,12 @@ void shouldReturnModels() throws Exception {
153159
when(modelService.getModels(eq(APP_GUID), anyLong())).thenReturn(List.of(responseDto, responseDto));
154160

155161
mockMvc.perform(request)
156-
.andExpect(status().isOk())
157-
.andExpect(content().string(mapper.writeValueAsString(List.of(responseDto, responseDto))));
162+
.andExpect(status().isOk())
163+
.andExpect(content().string(mapper.writeValueAsString(List.of(responseDto, responseDto))));
158164
}
159165

160-
@Test
166+
@ParameterizedTest
167+
@ValueSource(strings = {MODEL_NAME, "_model_-.[]"})
161168
void shouldReturnCreatedModel() throws Exception {
162169
val createDto = new ModelCreateDto();
163170
createDto.setName(MODEL_NAME);
@@ -170,8 +177,8 @@ void shouldReturnCreatedModel() throws Exception {
170177
.content(mapper.writeValueAsString(createDto));
171178

172179
val model = Model.builder()
173-
.name(MODEL_NAME)
174-
.build();
180+
.name(MODEL_NAME)
181+
.build();
175182

176183
val responseDto = new ModelResponseDto();
177184
responseDto.setName(MODEL_NAME);
@@ -180,8 +187,8 @@ void shouldReturnCreatedModel() throws Exception {
180187
when(modelMapper.toResponseDto(any(Model.class), eq(APP_GUID))).thenReturn(responseDto);
181188

182189
mockMvc.perform(createRequest)
183-
.andExpect(status().isCreated())
184-
.andExpect(content().string(mapper.writeValueAsString(responseDto)));
190+
.andExpect(status().isCreated())
191+
.andExpect(content().string(mapper.writeValueAsString(responseDto)));
185192
}
186193

187194
@Test
@@ -196,8 +203,8 @@ void shouldReturnUpdatedModel() throws Exception {
196203
.content(mapper.writeValueAsString(updateDto));
197204

198205
val model = Model.builder()
199-
.name(MODEL_NAME)
200-
.build();
206+
.name(MODEL_NAME)
207+
.build();
201208

202209
val responseDto = new ModelResponseDto();
203210
responseDto.setName(MODEL_NAME);
@@ -206,8 +213,8 @@ void shouldReturnUpdatedModel() throws Exception {
206213
when(modelMapper.toResponseDto(any(Model.class), eq(APP_GUID))).thenReturn(responseDto);
207214

208215
mockMvc.perform(createRequest)
209-
.andExpect(status().isOk())
210-
.andExpect(content().string(mapper.writeValueAsString(responseDto)));
216+
.andExpect(status().isOk())
217+
.andExpect(content().string(mapper.writeValueAsString(responseDto)));
211218
}
212219

213220
@Test
@@ -228,8 +235,8 @@ void shouldReturnUpdatedWithApiKeyModel() throws Exception {
228235
when(modelService.getModelDto(eq(APP_GUID), eq(MODEL_GUID), anyLong())).thenReturn(responseDto);
229236

230237
mockMvc.perform(request)
231-
.andExpect(status().isOk())
232-
.andExpect(content().string(mapper.writeValueAsString(responseDto)));
238+
.andExpect(status().isOk())
239+
.andExpect(content().string(mapper.writeValueAsString(responseDto)));
233240
}
234241

235242
@Test
@@ -245,6 +252,22 @@ void shouldReturnOkWhenDeleteModel() throws Exception {
245252
doNothing().when(modelService).deleteModel(eq(APP_GUID), eq(MODEL_GUID), anyLong());
246253

247254
mockMvc.perform(request)
248-
.andExpect(status().isOk());
255+
.andExpect(status().isOk());
256+
}
257+
258+
@Test
259+
@SneakyThrows
260+
void shouldReturnErrorMessageWhenNameContainsSpecialCharactersOnCreateNewModel() {
261+
val bodyWithEmptyName = new ModelCreateDto();
262+
bodyWithEmptyName.setName("\\new;model//");
263+
bodyWithEmptyName.setType("RECOGNITION");
264+
265+
mockMvc.perform(post(ADMIN + "/app/" + APP_GUID + "/model")
266+
.with(csrf())
267+
.with(user(buildUser()))
268+
.contentType(APPLICATION_JSON).content(mapper.writeValueAsString(bodyWithEmptyName)))
269+
.andExpect(status().isBadRequest())
270+
.andExpect(content().string("{\"message\":\"The name cannot contain the following special characters: ';', '/', '\\\\'\"," +
271+
"\"code\":36}"));
249272
}
250-
}
273+
}

0 commit comments

Comments
 (0)