diff --git a/.gitignore b/.gitignore index cd38e2e7b..1d2a5bee7 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,6 @@ target logs attachments *.patch - +*.evn +.evn diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..093c983dc --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +# Stage 1: build with Maven and OpenJDK 17 +FROM maven:3.9.5-eclipse-temurin-17 AS build + +WORKDIR /app + +COPY pom.xml . +RUN mvn dependency:go-offline + +COPY src ./src + +RUN mvn clean package -DskipTests + +# Stage 2: runtime image with OpenJDK 17 +FROM eclipse-temurin:17-jdk + +WORKDIR /app + +COPY --from=build /app/target/*.jar app.jar + +ENTRYPOINT ["java", "-jar", "app.jar"] \ No newline at end of file diff --git a/config/nginx.conf b/config/nginx.conf index 82b9e234d..df671b866 100644 --- a/config/nginx.conf +++ b/config/nginx.conf @@ -35,6 +35,6 @@ server { proxy_connect_timeout 150s; } location / { - try_files /view/404.html = 404; + try_files /view/404.html : 404; } } \ No newline at end of file diff --git a/pom.xml b/pom.xml index f6c152c68..cfb247a1a 100644 --- a/pom.xml +++ b/pom.xml @@ -28,6 +28,11 @@ org.springframework.boot spring-boot-starter-data-jpa + + com.h2database + h2 + test + org.springframework.boot spring-boot-starter-security diff --git a/resources/view/login.html b/resources/view/login.html index 8765ca8ff..c9e0f389d 100644 --- a/resources/view/login.html +++ b/resources/view/login.html @@ -48,14 +48,8 @@

Sign in

type="button"> - - - - - - + + diff --git a/resources/view/unauth/register.html b/resources/view/unauth/register.html index 2ba955045..399c0aa8a 100644 --- a/resources/view/unauth/register.html +++ b/resources/view/unauth/register.html @@ -77,14 +77,8 @@

Registration

type="button"> - - - - - - + + diff --git a/src/main/java/com/javarush/jira/JiraRushApplication.java b/src/main/java/com/javarush/jira/JiraRushApplication.java index 58be49fe7..1b28fe91e 100644 --- a/src/main/java/com/javarush/jira/JiraRushApplication.java +++ b/src/main/java/com/javarush/jira/JiraRushApplication.java @@ -11,7 +11,8 @@ @EnableCaching public class JiraRushApplication { - public static void main(String[] args) { + public static void main(String[] args) + { SpringApplication.run(JiraRushApplication.class, args); } } diff --git a/src/main/java/com/javarush/jira/bugtracking/attachment/FileUtil.java b/src/main/java/com/javarush/jira/bugtracking/attachment/FileUtil.java index 6cffbe175..4039c2668 100644 --- a/src/main/java/com/javarush/jira/bugtracking/attachment/FileUtil.java +++ b/src/main/java/com/javarush/jira/bugtracking/attachment/FileUtil.java @@ -15,6 +15,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; @UtilityClass public class FileUtil { @@ -24,15 +25,13 @@ public static void upload(MultipartFile multipartFile, String directoryPath, Str if (multipartFile.isEmpty()) { throw new IllegalRequestDataException("Select a file to upload."); } - - File dir = new File(directoryPath); - if (dir.exists() || dir.mkdirs()) { - File file = new File(directoryPath + fileName); - try (OutputStream outStream = new FileOutputStream(file)) { - outStream.write(multipartFile.getBytes()); - } catch (IOException ex) { - throw new IllegalRequestDataException("Failed to upload file" + multipartFile.getOriginalFilename()); - } + Path dirPath = Paths.get(directoryPath); + try { + Files.createDirectories(dirPath); + Path filePath = dirPath.resolve(fileName); + Files.write(filePath, multipartFile.getBytes(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + } catch (IOException ex) { + throw new IllegalRequestDataException("Failed to upload file: " + multipartFile.getOriginalFilename()); } } diff --git a/src/main/java/com/javarush/jira/common/internal/config/RestAuthenticationEntryPoint.java b/src/main/java/com/javarush/jira/common/internal/config/RestAuthenticationEntryPoint.java index 85a134319..70c4f684a 100644 --- a/src/main/java/com/javarush/jira/common/internal/config/RestAuthenticationEntryPoint.java +++ b/src/main/java/com/javarush/jira/common/internal/config/RestAuthenticationEntryPoint.java @@ -13,14 +13,20 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; @Component -@AllArgsConstructor public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint { - @Qualifier("handlerExceptionResolver") private final HandlerExceptionResolver resolver; private final RequestMappingHandlerMapping mapping; + public RestAuthenticationEntryPoint( + @Qualifier("handlerExceptionResolver") HandlerExceptionResolver resolver, + RequestMappingHandlerMapping mapping) { + this.resolver = resolver; + this.mapping = mapping; + } + @Override - public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws ServletException { + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) + throws ServletException { try { HandlerExecutionChain handler = mapping.getHandler(request); resolver.resolveException(request, response, handler == null ? null : handler.getHandler(), authException); @@ -28,4 +34,4 @@ public void commence(HttpServletRequest request, HttpServletResponse response, A throw new ServletException(e); } } -} +} \ No newline at end of file diff --git a/src/main/java/com/javarush/jira/login/internal/UserRepository.java b/src/main/java/com/javarush/jira/login/internal/UserRepository.java index 3ce3abe94..4a85acd9c 100644 --- a/src/main/java/com/javarush/jira/login/internal/UserRepository.java +++ b/src/main/java/com/javarush/jira/login/internal/UserRepository.java @@ -3,6 +3,7 @@ import com.javarush.jira.common.BaseRepository; import com.javarush.jira.common.error.NotFoundException; import com.javarush.jira.login.User; +import com.javarush.jira.login.AuthUser; import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; diff --git a/src/main/java/com/javarush/jira/login/internal/sociallogin/handler/VkOAuth2UserDataHandler.java b/src/main/java/com/javarush/jira/login/internal/sociallogin/handler/VkOAuth2UserDataHandler.java deleted file mode 100644 index e8e05be05..000000000 --- a/src/main/java/com/javarush/jira/login/internal/sociallogin/handler/VkOAuth2UserDataHandler.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.javarush.jira.login.internal.sociallogin.handler; - -import org.springframework.stereotype.Component; - -import java.util.List; -import java.util.Map; - -@Component("vk") -public class VkOAuth2UserDataHandler implements OAuth2UserDataHandler { - @Override - public String getFirstName(OAuth2UserData oAuth2UserData) { - return getAttribute(oAuth2UserData, "first_name"); - } - - @Override - public String getLastName(OAuth2UserData oAuth2UserData) { - return getAttribute(oAuth2UserData, "last_name"); - } - - @Override - public String getEmail(OAuth2UserData oAuth2UserData) { - return oAuth2UserData.getData("email"); - } - - private String getAttribute(OAuth2UserData oAuth2UserData, String name) { - List> attributesResponse = oAuth2UserData.getData("response"); - if (attributesResponse != null) { - Map attributes = attributesResponse.get(0); - if (attributes != null) { - return (String) attributes.get(name); - } - } - return null; - } -} diff --git a/src/main/java/com/javarush/jira/login/internal/sociallogin/handler/YandexOAuth2UserDataHandler.java b/src/main/java/com/javarush/jira/login/internal/sociallogin/handler/YandexOAuth2UserDataHandler.java deleted file mode 100644 index e8ea1ac1d..000000000 --- a/src/main/java/com/javarush/jira/login/internal/sociallogin/handler/YandexOAuth2UserDataHandler.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.javarush.jira.login.internal.sociallogin.handler; - -import org.springframework.stereotype.Component; - -@Component("yandex") -public class YandexOAuth2UserDataHandler implements OAuth2UserDataHandler { - @Override - public String getFirstName(OAuth2UserData oAuth2UserData) { - return oAuth2UserData.getData("first_name"); - } - - @Override - public String getLastName(OAuth2UserData oAuth2UserData) { - return oAuth2UserData.getData("last_name"); - } - - @Override - public String getEmail(OAuth2UserData oAuth2UserData) { - return oAuth2UserData.getData("default_email"); - } -} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 7fcba1570..4a1ef066e 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -8,6 +8,9 @@ app: max-pool-size: 100 spring: + + config: + import: optional:classpath:privacy.properties init: mode: never jpa: @@ -25,13 +28,10 @@ spring: default_batch_fetch_size: 20 # https://stackoverflow.com/questions/21257819/what-is-the-difference-between-hibernate-jdbc-fetch-size-and-hibernate-jdbc-batc jdbc.batch_size: 20 - datasource: - url: jdbc:postgresql://localhost:5432/jira - username: jira - password: JiraRush - liquibase: changeLog: "classpath:db/changelog.sql" + clear-checksums: true + # Jackson Fields Serialization jackson: @@ -46,59 +46,6 @@ spring: cache-names: users caffeine.spec: maximumSize=10000,expireAfterAccess=5m - security: - oauth2: - client: - registration: - github: - client-id: 3d0d8738e65881fff266 - client-secret: 0f97031ce6178b7dfb67a6af587f37e222a16120 - scope: - - email - google: - client-id: 329113642700-f8if6pu68j2repq3ef6umd5jgiliup60.apps.googleusercontent.com - client-secret: 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 - scope: - - email - - profile - vk: - client-id: 51562377 - client-secret: jNM1YHQy1362Mqs49wUN - client-name: Vkontakte - redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}" - client-authentication-method: client_secret_post - authorization-grant-type: authorization_code - scope: email - yandex: - client-id: 2f3395214ba84075956b76a34b231985 - client-secret: ed236c501e444a609b0f419e5e88f1e1 - client-name: Yandex - redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}" - authorization-grant-type: authorization_code - gitlab: - client-id: b8520a3266089063c0d8261cce36971defa513f5ffd9f9b7a3d16728fc83a494 - client-secret: e72c65320cf9d6495984a37b0f9cc03ec46be0bb6f071feaebbfe75168117004 - client-name: GitLab - redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}" - authorization-grant-type: authorization_code - scope: read_user - provider: - vk: - authorization-uri: https://oauth.vk.com/authorize - token-uri: https://oauth.vk.com/access_token - user-info-uri: https://api.vk.com/method/users.get?v=8.1 - user-name-attribute: response - yandex: - authorization-uri: https://oauth.yandex.ru/authorize - token-uri: https://oauth.yandex.ru/token - user-info-uri: https://login.yandex.ru/info - user-name-attribute: login - gitlab: - authorization-uri: https://gitlab.com/oauth/authorize - token-uri: https://gitlab.com/oauth/token - user-info-uri: https://gitlab.com/api/v4/user - user-name-attribute: email - sql: init: mode: always @@ -110,10 +57,6 @@ spring: starttls: enable: true auth: true - host: smtp.gmail.com - username: jira4jr@gmail.com - password: zdfzsrqvgimldzyj - port: 587 thymeleaf.check-template-location: false mvc.throw-exception-if-no-handler-found: true @@ -134,4 +77,4 @@ server: charset: UTF-8 # Charset of HTTP requests and responses. Added to the "Content-Type" header if not set explicitly enabled: true # Enable http encoding support force: true -springdoc.swagger-ui.path: /doc +springdoc.swagger-ui.path: /doc \ No newline at end of file diff --git a/src/main/resources/db/changelog.sql b/src/main/resources/db/changelog.sql index 68591336d..8ade5c94d 100644 --- a/src/main/resources/db/changelog.sql +++ b/src/main/resources/db/changelog.sql @@ -218,7 +218,7 @@ values ('task', 'Task', 2), ('mobile', 'Mobile', 0), ('phone', 'Phone', 0), ('website', 'Website', 0), - ('vk', 'VK', 0), + ('linkedin', 'LinkedIn', 0), ('github', 'GitHub', 0), -- PRIORITY diff --git a/src/main/resources/privacy.properties b/src/main/resources/privacy.properties new file mode 100644 index 000000000..665664a0e --- /dev/null +++ b/src/main/resources/privacy.properties @@ -0,0 +1,33 @@ +# GitHub OAuth2 +spring.security.oauth2.client.registration.github.client-id=3d0d8738e65881fff266 +spring.security.oauth2.client.registration.github.client-secret=0f97031ce6178b7dfb67a6af587f37e222a16120 +spring.security.oauth2.client.registration.github.scope=email + +# Google OAuth2 +spring.security.oauth2.client.registration.google.client-id=329113642700-f8if6pu68j2repq3ef6umd5jgiliup60.apps.googleusercontent.co +spring.security.oauth2.client.registration.google.client-secret=OCSPX-OCd-JBle221TaIBohCzQN9m9E-ap +spring.security.oauth2.client.registration.google.scope=email,profile + +# GitLab OAuth2 (registration) +spring.security.oauth2.client.registration.gitlab.client-id=b8520a3266089063c0d8261cce36971defa513f5ffd9f9b7a3d16728fc83a494 +spring.security.oauth2.client.registration.gitlab.client-secret=e72c65320cf9d6495984a37b0f9cc03ec46be0bb6f071feaebbfe75168117004 +spring.security.oauth2.client.registration.gitlab.client-name=GitLab +spring.security.oauth2.client.registration.gitlab.redirect-uri={baseUrl}/login/oauth2/code/{registrationId} +spring.security.oauth2.client.registration.gitlab.authorization-grant-type=authorization_code +spring.security.oauth2.client.registration.gitlab.scope=read_user + +# GitLab OAuth2 (provider) +spring.security.oauth2.client.provider.gitlab.authorization-uri=https://gitlab.com/oauth/authorize +spring.security.oauth2.client.provider.gitlab.token-uri=https://gitlab.com/oauth/token +spring.security.oauth2.client.provider.gitlab.user-info-uri=https://gitlab.com/api/v4/user +spring.security.oauth2.client.provider.gitlab.user-name-attribute=email + +#datasource +spring.datasource.url=jdbc:postgresql://localhost:5432/jira +spring.datasource.username=jira +spring.datasource.password=JiraRush +#mail +spring.mail.host=smtp.gmail.com +spring.mail.username=jira4jr@gmail.com +spring.mail.password=zdfzsrqvgimldzyj +spring.mail.port=587 \ No newline at end of file diff --git a/src/test/java/com/javarush/jira/AbstractControllerTest.java b/src/test/java/com/javarush/jira/AbstractControllerTest.java index 5981bae53..1e68a98a2 100644 --- a/src/test/java/com/javarush/jira/AbstractControllerTest.java +++ b/src/test/java/com/javarush/jira/AbstractControllerTest.java @@ -9,7 +9,7 @@ import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; //https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-testing-spring-boot-applications -@Sql(scripts = {"classpath:db/changelog.sql", "classpath:data.sql"}, config = @SqlConfig(encoding = "UTF-8")) +@Sql(scripts = {"classpath:db/changelog.sql", "classpath:data-test.sql"}, config = @SqlConfig(encoding = "UTF-8")) @AutoConfigureMockMvc //https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-testing-spring-boot-applications-testing-with-mock-environment public abstract class AbstractControllerTest extends BaseTests { @@ -20,4 +20,5 @@ public abstract class AbstractControllerTest extends BaseTests { protected ResultActions perform(MockHttpServletRequestBuilder builder) throws Exception { return mockMvc.perform(builder); } + } diff --git a/src/test/java/com/javarush/jira/profile/internal/web/ProfileRestControllerTest.java b/src/test/java/com/javarush/jira/profile/internal/web/ProfileRestControllerTest.java index a6fd5e3bf..cea916ea2 100644 --- a/src/test/java/com/javarush/jira/profile/internal/web/ProfileRestControllerTest.java +++ b/src/test/java/com/javarush/jira/profile/internal/web/ProfileRestControllerTest.java @@ -1,8 +1,70 @@ package com.javarush.jira.profile.internal.web; +import com.fasterxml.jackson.databind.ObjectMapper; import com.javarush.jira.AbstractControllerTest; +import com.javarush.jira.login.AuthUser; +import com.javarush.jira.login.User; +import com.javarush.jira.login.internal.UserRepository; +import com.javarush.jira.profile.ContactTo; +import com.javarush.jira.profile.ProfileTo; +import com.javarush.jira.profile.internal.ProfileRepository; +import com.javarush.jira.profile.internal.model.Contact; +import com.javarush.jira.profile.internal.model.Profile; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.HashSet; +import java.util.Set; + +import static com.javarush.jira.profile.internal.web.ProfileRestController.REST_URL; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; class ProfileRestControllerTest extends AbstractControllerTest { + @Autowired + private ObjectMapper objectMapper; + @Autowired + // it's fake error + private MockMvc mvc; + @Autowired + private UserRepository userRepository; + @Autowired + private ProfileRepository profileRepository; + @Test + public void getWithRealUser() throws Exception { + User user = userRepository.findById(1L).get(); + AuthUser authUser = new AuthUser(user); + mvc.perform(MockMvcRequestBuilders.get(REST_URL) + .with(user(authUser))) + .andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)); + } + + @Test + public void updateProfileWithUnRealUser() throws Exception { + User user = new User(101110L,"1234111115678@gmail.com","dgd111tg043","123","321","33322211"); + Profile profile = profileRepository.findById(1L).get(); + Set contactTos = new HashSet<>(); + for (Contact contact : profile.getContacts()) { + ContactTo contactTo = new ContactTo(contact.getCode(),contact.getValue()); + contactTos.add(contactTo); + } + ProfileTo profileTo = new ProfileTo(1L, Set.of(String.valueOf(profile.getMailNotifications())), contactTos); + AuthUser authUser = new AuthUser(user); + mvc.perform(MockMvcRequestBuilders.put(REST_URL).with(user(authUser)).contentType(MediaType.APPLICATION_JSON).content( + objectMapper.writeValueAsString(profileTo) + )).andExpect(status().isUnprocessableEntity()); + } + } \ No newline at end of file diff --git a/src/test/resources/data.sql b/src/test/resources/data-test.sql similarity index 100% rename from src/test/resources/data.sql rename to src/test/resources/data-test.sql diff --git a/src/test/resources/schema-test.sql b/src/test/resources/schema-test.sql new file mode 100644 index 000000000..5bc3a9259 --- /dev/null +++ b/src/test/resources/schema-test.sql @@ -0,0 +1,18 @@ +CREATE TABLE users ( + id BIGINT PRIMARY KEY, + email VARCHAR(255), + password VARCHAR(255), + endpoint TIMESTAMP, + enabled BOOLEAN +); +CREATE TABLE profile ( + id BIGINT PRIMARY KEY, + user_id BIGINT NOT NULL, + mail_notifications INTEGER, + FOREIGN KEY (user_id) REFERENCES users(id) +); +INSERT INTO users (id, email, password, endpoint, enabled) +VALUES (1, 'testuser@example.com', 'password', CURRENT_TIMESTAMP, true); + +INSERT INTO profile (id, user_id, mail_notifications) +VALUES (1, 1, 1); \ No newline at end of file