Compare commits
No commits in common. "master" and "feature/auth" have entirely different histories.
master
...
feature/au
328
README.md
328
README.md
|
|
@ -19,19 +19,6 @@
|
||||||
implementation("ru.touchin:common")
|
implementation("ru.touchin:common")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Project Gradle tasks
|
|
||||||
### Detekt:
|
|
||||||
- `gradle $project:detekt` - detect not formatted, complex code.
|
|
||||||
|
|
||||||
Reports are stored in "$pwd/build/reports/kotlin-detekt-${project.name}.html".
|
|
||||||
|
|
||||||
### DiKTat:
|
|
||||||
- `gradle :diktatCheck` - detect not formatted code of "kt", "kts" files;
|
|
||||||
- `gradle :diktatFix` - if possible, fix not formatted code. Known issues: full fix may require 1+ launch in order to apply all rules; some rules potentially may break code syntax.
|
|
||||||
|
|
||||||
By setting environment variable `TASKS_FILE_REPORT_ENABLED`(true, false) you may configure raw console output or html file as report.
|
|
||||||
Reports are stored in "$pwd/build/reports/diktat-report.html".
|
|
||||||
|
|
||||||
## common
|
## common
|
||||||
|
|
||||||
|
|
@ -81,106 +68,6 @@ Reports are stored in "$pwd/build/reports/diktat-report.html".
|
||||||
|
|
||||||
Утилиты для тестирования репозиториев
|
Утилиты для тестирования репозиториев
|
||||||
|
|
||||||
## codestyle-archunit
|
|
||||||
|
|
||||||
Набор правил для поддержки оформления архитектуры.
|
|
||||||
|
|
||||||
#### Список доступных правил
|
|
||||||
- `ru.touchin.codestyle.archunit.rules.ClassNamingArchRules`
|
|
||||||
- `ru.touchin.codestyle.archunit.rules.ClassPackagingArchRules`
|
|
||||||
|
|
||||||
|
|
||||||
#### Gradle plugin
|
|
||||||
Настройка и применение совместно с [ArchUnit Gradle Plugin](https://github.com/societe-generale/arch-unit-gradle-plugin).
|
|
||||||
|
|
||||||
Действие `checkRules` для проверки соответствие правилам запускается при операциях сборки, по умолчанию.
|
|
||||||
Вручную можно вызвать командой ``gradle :checkRules`` для нужного модуля.
|
|
||||||
Добавить его можно следующим образом на примере установки в рутовый gradle.build проекта:
|
|
||||||
|
|
||||||
Groovy DSL:
|
|
||||||
```groovy
|
|
||||||
buildscript {
|
|
||||||
dependencies {
|
|
||||||
classpath "com.societegenerale.commons:arch-unit-gradle-plugin:3.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
subprojects {
|
|
||||||
dependencyManagement {
|
|
||||||
dependencies {
|
|
||||||
dependency "com.tngtech.archunit:archunit:1.0.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
apply plugin: "java"
|
|
||||||
apply plugin: "com.societegenerale.commons.arch-unit-gradle-plugin"
|
|
||||||
|
|
||||||
archUnit {
|
|
||||||
mainScopePath = "/classes/kotlin/main" // or "/classes/java/main"
|
|
||||||
testScopePath = "/classes/kotlin/test" // or "/classes/java/test"
|
|
||||||
|
|
||||||
var applyType = applyOn("ru.touchin", "main")
|
|
||||||
configurableRules = [
|
|
||||||
configurableRule(
|
|
||||||
"ru.touchin.codestyle.archunit.rules.ClassNamingArchRules",
|
|
||||||
applyType,
|
|
||||||
),
|
|
||||||
configurableRule(
|
|
||||||
"ru.touchin.codestyle.archunit.rules.ClassPackagingArchRules",
|
|
||||||
applyType,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
archUnitExtraLib "ru.touchin:codestyle-archunit" // or archUnitExtraLib project(":codestyle-archunit")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Kotlin DSL:
|
|
||||||
```kotlin
|
|
||||||
plugins {
|
|
||||||
id("com.societegenerale.commons.arch-unit-gradle-plugin") version "3.0.0"
|
|
||||||
}
|
|
||||||
subprojects {
|
|
||||||
configure<DependencyManagementExtension> {
|
|
||||||
dependencies {
|
|
||||||
dependency("com.tngtech.archunit:archunit:1.0.1")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
apply(plugin = "java")
|
|
||||||
apply(plugin = "com.societegenerale.commons.arch-unit-gradle-plugin")
|
|
||||||
|
|
||||||
archUnit {
|
|
||||||
mainScopePath = "/classes/kotlin/main" // or "/classes/java/main"
|
|
||||||
testScopePath = "/classes/kotlin/test" // or "/classes/java/test"
|
|
||||||
|
|
||||||
configurableRules = listOf(
|
|
||||||
"ru.touchin.codestyle.archunit.rules.ClassNamingArchRules",
|
|
||||||
"ru.touchin.codestyle.archunit.rules.ClassPackagingArchRules"
|
|
||||||
).map { package ->
|
|
||||||
configurableRule(
|
|
||||||
package,
|
|
||||||
applyOn("ru.touchin", "main")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
archUnitExtraLib("ru.touchin:codestyle-archunit") // or archUnitExtraLib(project(":codestyle-archunit"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Отключить проверки на таске помимо конфигурирования `configurableRule` можно также таким образом:
|
|
||||||
```kotlin
|
|
||||||
// clear action launch for root project to avoid exception
|
|
||||||
tasks.checkRules.configure {
|
|
||||||
actions.clear()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## logger
|
## logger
|
||||||
|
|
||||||
Основные компоненты логирования:
|
Основные компоненты логирования:
|
||||||
|
|
@ -202,17 +89,11 @@ Interceptor для логирования запросов/ответов.
|
||||||
|
|
||||||
## exception-handler-spring-web
|
## exception-handler-spring-web
|
||||||
|
|
||||||
Перехватывает ошибки сервера, определяет код ошибки и возвращает их в правильный `response`.
|
Перехватывает ошибки сервера, определяет код ошибки и возвращает их в правильный `response`
|
||||||
Подключается с помощью аннотации `@EnableSpringExceptionHandler`
|
|
||||||
|
|
||||||
## exception-handler-logger-spring-web
|
## exception-handler-logger-spring-web
|
||||||
|
|
||||||
Добавляет логирование в обработку ошибок.
|
Добавляет логирование в обработку ошибок
|
||||||
Подключается с помощью аннотации `@EnableSpringExceptionHandlerLogger` до подключения основного модуля.
|
|
||||||
|
|
||||||
## validation-spring
|
|
||||||
|
|
||||||
Добавляет аннотации для валидации запросов.
|
|
||||||
|
|
||||||
## version-spring-web
|
## version-spring-web
|
||||||
|
|
||||||
|
|
@ -242,11 +123,11 @@ Interceptor для логирования запросов/ответов.
|
||||||
|
|
||||||
Модуль для хранения настроек
|
Модуль для хранения настроек
|
||||||
|
|
||||||
## security-authorization-server-core
|
## auth-core
|
||||||
|
|
||||||
Модуль авторизации
|
Модуль авторизации
|
||||||
|
|
||||||
## security-authorization-server-jwt-core
|
## auth-jwt-core
|
||||||
|
|
||||||
Добавляет поддержку jwt-токенов (создание/хранение). Для работы этого модуля требуется прописать в пропертях:
|
Добавляет поддержку jwt-токенов (создание/хранение). Для работы этого модуля требуется прописать в пропертях:
|
||||||
|
|
||||||
|
|
@ -267,204 +148,3 @@ token.refresh:
|
||||||
prefix: RT-
|
prefix: RT-
|
||||||
timeToLive: PT2H # 2 hours
|
timeToLive: PT2H # 2 hours
|
||||||
```
|
```
|
||||||
|
|
||||||
Генерация ключей:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
openssl genrsa -out private.pem 4096
|
|
||||||
openssl rsa -in private.pem -pubout -out public.pem
|
|
||||||
openssl pkcs8 -topk8 -inform PEM -in private.pem -out private_key.pem -nocrypt
|
|
||||||
|
|
||||||
cat private_key.pem
|
|
||||||
cat public.pem
|
|
||||||
```
|
|
||||||
|
|
||||||
## security-authorization-server-oauth2-metadata
|
|
||||||
|
|
||||||
OAuth2 metadata support.
|
|
||||||
|
|
||||||
## security-jwt-common
|
|
||||||
|
|
||||||
JWT related utilities.
|
|
||||||
|
|
||||||
## security-resource-server-default-configuration
|
|
||||||
|
|
||||||
Default configuration for the Spring OAuth2 resource server with JWT auth.
|
|
||||||
|
|
||||||
## security-resource-server-custom-configuration
|
|
||||||
|
|
||||||
Custom configuration for the Spring OAuth2 resource server with JWT auth. Requires the following properties:
|
|
||||||
|
|
||||||
``` yaml
|
|
||||||
token.access:
|
|
||||||
issuer: ${app.issuer}
|
|
||||||
signatureAlgorithm: RS256
|
|
||||||
keyPair:
|
|
||||||
public: |
|
|
||||||
-----BEGIN PUBLIC KEY-----
|
|
||||||
-----END PUBLIC KEY-----
|
|
||||||
```
|
|
||||||
|
|
||||||
## security-resource-server-test-configuration
|
|
||||||
|
|
||||||
Disables Spring OAuth2 resource server for testing.
|
|
||||||
|
|
||||||
## s3-storage
|
|
||||||
|
|
||||||
Amazon S3 support.
|
|
||||||
|
|
||||||
## server-info-spring-web
|
|
||||||
|
|
||||||
Allow include headers with information about the server in responses
|
|
||||||
|
|
||||||
To get started you need:
|
|
||||||
1) Add annotation to configuration
|
|
||||||
2) Add property to yml/properties file:
|
|
||||||
```
|
|
||||||
server.info:
|
|
||||||
buildVersion: ${buildVersion}
|
|
||||||
```
|
|
||||||
3) Implement ServerInfoService (optional. If you want to add other headers)
|
|
||||||
4) Add dir with impl ServerInfoService in ComponentScan annotation
|
|
||||||
|
|
||||||
## push-message-provider
|
|
||||||
|
|
||||||
Интерфейсы и компоненты для модулей по обеспечению интеграции с сервисами отправки пуш-уведомлений. Является необходимой зависимостью для использования провайдеров.
|
|
||||||
|
|
||||||
Далее рассматривается пример использования подключаемых модулей-провайдеров.
|
|
||||||
``` kotlin
|
|
||||||
@Service
|
|
||||||
class PushSendingService(
|
|
||||||
private val pushMessageProviderServiceFactory: PushMessageProviderServiceFactory
|
|
||||||
) {
|
|
||||||
|
|
||||||
fun sendPushMessage() {
|
|
||||||
val yourPushToken = "pushTokenForChecking"
|
|
||||||
val platform = PlatformType.ANDROID_GOOGLE
|
|
||||||
|
|
||||||
val pushMessageProvider: PushMessageProviderService = pushMessageProviderServiceFactory.get(platform)
|
|
||||||
|
|
||||||
val result = pushMessageProvider.check( // Проверка валидности токена для обозначения целесообразности отправки
|
|
||||||
PushTokenCheck(
|
|
||||||
pushToken = yourPushToken
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (result.status == PushTokenStatus.VALID) { // Токен валиден, PushMessageProviderService интегрирован в систему
|
|
||||||
// Отправка пуш-уведомления
|
|
||||||
pushMessageProvider.send(
|
|
||||||
PushTokenMessage(
|
|
||||||
token = yourPushToken,
|
|
||||||
pushMessageNotification = PushMessageNotification(
|
|
||||||
title = "Your PushMessage",
|
|
||||||
description = "Provided by PushMessageProviderService",
|
|
||||||
imageUrl = null
|
|
||||||
),
|
|
||||||
data = mapOf(
|
|
||||||
"customKey" to "customData"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## push-message-provider-fcm
|
|
||||||
|
|
||||||
Модуль по обеспечению интеграции с Firebase Cloud Messaging.
|
|
||||||
1) Подключение компонентов Spring осуществляется при помощи аннотации `@EnablePushMessageProviderFcm`.
|
|
||||||
2) Необходимо добавление конфигурации для модуля с выбранным способом хранения данных для авторизации. Пример файла конфигурации в формате yaml:
|
|
||||||
``` yaml
|
|
||||||
push-message-provider:
|
|
||||||
platformProviders:
|
|
||||||
ANDROID_GOOGLE:
|
|
||||||
- FCM
|
|
||||||
IOS:
|
|
||||||
- FCM
|
|
||||||
fcm:
|
|
||||||
appName: yourAppName
|
|
||||||
auth:
|
|
||||||
# Выбранный тип авторизации
|
|
||||||
client:
|
|
||||||
readTimeout: 10s
|
|
||||||
connectionTimeout: 1s
|
|
||||||
```
|
|
||||||
3) Настраивается способ предоставления авторизации для Firebase Cloud Messaging.
|
|
||||||
|
|
||||||
А) Токен доступа из консоли Google, добавляемый в конфигурацию настроек:
|
|
||||||
``` yaml
|
|
||||||
auth:
|
|
||||||
token:
|
|
||||||
value: testValue
|
|
||||||
expiresAt: 2023-01-01 23:59:59 +00:00
|
|
||||||
```
|
|
||||||
B) Данные в файле из консоли Firebase, добавляемые в resources с обозначением пути в конфигурации настроек:
|
|
||||||
``` yaml
|
|
||||||
auth:
|
|
||||||
credentialsFile:
|
|
||||||
path: credentials/firebase-admin.json
|
|
||||||
```
|
|
||||||
C) Данные из файла консоли Firebase, добавляемые в конфигурацию настроек:
|
|
||||||
``` yaml
|
|
||||||
auth:
|
|
||||||
credentialsData:
|
|
||||||
type: service_account
|
|
||||||
projectId: yourProjectId
|
|
||||||
privateKeyId: yourPrivateKeyId
|
|
||||||
privateKey: |
|
|
||||||
-----BEGIN PRIVATE KEY-----
|
|
||||||
-----END PRIVATE KEY-----
|
|
||||||
clientEmail: yourClientEmail
|
|
||||||
clientId: yourClientId
|
|
||||||
authUri: yourAuthUri
|
|
||||||
tokenUri: yourTokenUri
|
|
||||||
authProviderX509CertUrl: yourAuthProviderX509CertUrl
|
|
||||||
clientX509CertUrl: yourClientX509CertUrl
|
|
||||||
```
|
|
||||||
|
|
||||||
## push-message-provider-hpk
|
|
||||||
|
|
||||||
Модуль по обеспечению интеграции с Huawei Push Kit.
|
|
||||||
|
|
||||||
1) Подключение нового провайдера осуществляется при помощи аннотации `@EnablePushMessageProviderHpk`.
|
|
||||||
2) Для логирования запросов к сервису HPK нужно встроить в контейнер Spring собственный `WebClientLogger` из модуля `logger-spring-web` или же использовать стандартный посредством импорта конфигурации:
|
|
||||||
``` kotlin
|
|
||||||
@Import(
|
|
||||||
SpringLoggerConfiguration::class,
|
|
||||||
SpringLoggerWebConfiguration::class
|
|
||||||
)
|
|
||||||
class YourConfiguration
|
|
||||||
```
|
|
||||||
3) Нужно добавить конфигурацию для считывания модулем. Пример файла в формате yaml:
|
|
||||||
``` yaml
|
|
||||||
push-message-provider:
|
|
||||||
platformProviders:
|
|
||||||
ANDROID_HUAWEI:
|
|
||||||
- HPK
|
|
||||||
hpk:
|
|
||||||
web-services:
|
|
||||||
client-id: yourClientId
|
|
||||||
oauth:
|
|
||||||
client-secret: yourClientSecret
|
|
||||||
url: https://oauth-login.cloud.huawei.com/oauth2/v3/
|
|
||||||
http:
|
|
||||||
connection-timeout: 1s
|
|
||||||
read-timeout: 10s
|
|
||||||
write-timeout: 10s
|
|
||||||
ssl: # Опциональная структура
|
|
||||||
handshake-timeout: 1s
|
|
||||||
notify-read-timeout: 1s
|
|
||||||
notify-flush-timeout: 1s
|
|
||||||
hpk:
|
|
||||||
url: https://push-api.cloud.huawei.com/v1/
|
|
||||||
http:
|
|
||||||
connection-timeout: 1s
|
|
||||||
read-timeout: 10s
|
|
||||||
write-timeout: 10s
|
|
||||||
ssl: # Опциональная структура
|
|
||||||
handshake-timeout: 1s
|
|
||||||
notify-read-timeout: 1s
|
|
||||||
notify-flush-timeout: 1s
|
|
||||||
```
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ dependencies {
|
||||||
runtimeOnly("org.postgresql:postgresql")
|
runtimeOnly("org.postgresql:postgresql")
|
||||||
|
|
||||||
api(project(":common"))
|
api(project(":common"))
|
||||||
api(project(":common-device"))
|
|
||||||
|
|
||||||
api(project(":common-spring-jpa"))
|
api(project(":common-spring-jpa"))
|
||||||
|
|
||||||
|
|
@ -8,7 +8,7 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder
|
import org.springframework.security.crypto.password.PasswordEncoder
|
||||||
|
|
||||||
@ComponentScan("ru.touchin.auth.core")
|
@ComponentScan("ru.touchin.auth.core")
|
||||||
@ConfigurationPropertiesScan("ru.touchin.auth.core")
|
@ConfigurationPropertiesScan
|
||||||
class AuthCoreConfiguration {
|
class AuthCoreConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
@file:Suppress("unused")
|
||||||
|
package ru.touchin.auth.core.configurations
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.domain.EntityScan
|
||||||
|
import org.springframework.cache.annotation.EnableCaching
|
||||||
|
import org.springframework.data.jpa.repository.config.EnableJpaRepositories
|
||||||
|
import ru.touchin.common.spring.jpa.EnableJpaAuditingExtra
|
||||||
|
|
||||||
|
@EntityScan("ru.touchin.auth.core")
|
||||||
|
@EnableJpaRepositories("ru.touchin.auth.core")
|
||||||
|
@EnableCaching
|
||||||
|
@EnableJpaAuditingExtra
|
||||||
|
class AuthCoreDatabaseConfiguration
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package ru.touchin.auth.core.device.dto
|
package ru.touchin.auth.core.device.dto
|
||||||
|
|
||||||
import ru.touchin.common.devices.enums.DevicePlatform
|
import ru.touchin.auth.core.device.dto.enums.DevicePlatform
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
data class Device(
|
data class Device(
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package ru.touchin.auth.core.device.dto.enums
|
||||||
|
|
||||||
|
enum class DevicePlatform {
|
||||||
|
Android, Huawei, Apple
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
package ru.touchin.auth.core.device.models
|
package ru.touchin.auth.core.device.models
|
||||||
|
|
||||||
import ru.touchin.auth.core.configurations.AuthCoreDatabaseConfiguration.Companion.SCHEMA
|
import ru.touchin.auth.core.device.dto.enums.DevicePlatform
|
||||||
import ru.touchin.auth.core.user.models.UserEntity
|
import ru.touchin.auth.core.user.models.UserEntity
|
||||||
import ru.touchin.common.devices.enums.DevicePlatform
|
|
||||||
import ru.touchin.common.spring.jpa.models.AuditableUuidIdEntity
|
import ru.touchin.common.spring.jpa.models.AuditableUuidIdEntity
|
||||||
import javax.persistence.Entity
|
import javax.persistence.Entity
|
||||||
import javax.persistence.JoinColumn
|
import javax.persistence.JoinColumn
|
||||||
|
|
@ -11,7 +10,7 @@ import javax.persistence.ManyToMany
|
||||||
import javax.persistence.Table
|
import javax.persistence.Table
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "devices", schema = SCHEMA)
|
@Table(name = "devices")
|
||||||
class DeviceEntity: AuditableUuidIdEntity() {
|
class DeviceEntity: AuditableUuidIdEntity() {
|
||||||
|
|
||||||
lateinit var platform: DevicePlatform
|
lateinit var platform: DevicePlatform
|
||||||
|
|
@ -19,26 +18,11 @@ class DeviceEntity: AuditableUuidIdEntity() {
|
||||||
@ManyToMany
|
@ManyToMany
|
||||||
@JoinTable(
|
@JoinTable(
|
||||||
name = "devices_users",
|
name = "devices_users",
|
||||||
schema = SCHEMA,
|
|
||||||
joinColumns = [JoinColumn(name = "device_id")],
|
joinColumns = [JoinColumn(name = "device_id")],
|
||||||
inverseJoinColumns = [JoinColumn(name = "user_id")]
|
inverseJoinColumns = [JoinColumn(name = "user_id")]
|
||||||
)
|
)
|
||||||
lateinit var users: Set<UserEntity>
|
lateinit var users: Set<UserEntity>
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (other == null || this.javaClass != other.javaClass) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
other as DeviceEntity
|
|
||||||
|
|
||||||
return this.id == other.id
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
return this.id.hashCode()
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun create(platform: DevicePlatform): DeviceEntity {
|
fun create(platform: DevicePlatform): DeviceEntity {
|
||||||
return DeviceEntity().apply {
|
return DeviceEntity().apply {
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package ru.touchin.auth.core.device.services
|
package ru.touchin.auth.core.device.services
|
||||||
|
|
||||||
import ru.touchin.auth.core.device.dto.Device
|
import ru.touchin.auth.core.device.dto.Device
|
||||||
import ru.touchin.common.devices.enums.DevicePlatform
|
import ru.touchin.auth.core.device.dto.enums.DevicePlatform
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
interface DeviceCoreService {
|
interface DeviceCoreService {
|
||||||
|
|
@ -6,9 +6,9 @@ import org.springframework.transaction.annotation.Transactional
|
||||||
import ru.touchin.auth.core.device.converters.DeviceConverter.toDto
|
import ru.touchin.auth.core.device.converters.DeviceConverter.toDto
|
||||||
import ru.touchin.auth.core.device.dto.Device
|
import ru.touchin.auth.core.device.dto.Device
|
||||||
import ru.touchin.auth.core.device.models.DeviceEntity
|
import ru.touchin.auth.core.device.models.DeviceEntity
|
||||||
|
import ru.touchin.auth.core.device.dto.enums.DevicePlatform
|
||||||
import ru.touchin.auth.core.device.repository.DeviceRepository
|
import ru.touchin.auth.core.device.repository.DeviceRepository
|
||||||
import ru.touchin.auth.core.device.repository.findByIdOrThrow
|
import ru.touchin.auth.core.device.repository.findByIdOrThrow
|
||||||
import ru.touchin.common.devices.enums.DevicePlatform
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package ru.touchin.auth.core.policy.services
|
package ru.touchin.auth.core.policy.services
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import ru.touchin.auth.core.policy.dto.RegistrationPolicy
|
import ru.touchin.auth.core.policy.dto.RegistrationPolicy
|
||||||
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package ru.touchin.auth.core.scope.models
|
||||||
|
|
||||||
|
import ru.touchin.common.spring.jpa.models.BaseEntity
|
||||||
|
import javax.persistence.Entity
|
||||||
|
import javax.persistence.Id
|
||||||
|
import javax.persistence.Table
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "scopes")
|
||||||
|
class ScopeEntity : BaseEntity() {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
lateinit var name: String
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
@file:Suppress("unused")
|
|
||||||
|
|
||||||
package ru.touchin.auth.core.scope.models
|
package ru.touchin.auth.core.scope.models
|
||||||
|
|
||||||
import ru.touchin.auth.core.configurations.AuthCoreDatabaseConfiguration.Companion.SCHEMA
|
|
||||||
import ru.touchin.common.spring.jpa.models.BaseUuidIdEntity
|
import ru.touchin.common.spring.jpa.models.BaseUuidIdEntity
|
||||||
import javax.persistence.Entity
|
import javax.persistence.Entity
|
||||||
import javax.persistence.JoinColumn
|
import javax.persistence.JoinColumn
|
||||||
|
|
@ -10,7 +7,7 @@ import javax.persistence.ManyToOne
|
||||||
import javax.persistence.Table
|
import javax.persistence.Table
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "scope_groups", schema = SCHEMA)
|
@Table(name = "scope_groups")
|
||||||
class ScopeGroupEntity : BaseUuidIdEntity() {
|
class ScopeGroupEntity : BaseUuidIdEntity() {
|
||||||
|
|
||||||
lateinit var groupName: String
|
lateinit var groupName: String
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
package ru.touchin.auth.core.user.models
|
package ru.touchin.auth.core.user.models
|
||||||
|
|
||||||
import ru.touchin.auth.core.configurations.AuthCoreDatabaseConfiguration.Companion.SCHEMA
|
|
||||||
import ru.touchin.auth.core.user.dto.enums.IdentifierType
|
import ru.touchin.auth.core.user.dto.enums.IdentifierType
|
||||||
import ru.touchin.common.spring.jpa.models.AuditableUuidIdEntity
|
import ru.touchin.common.spring.jpa.models.AuditableUuidIdEntity
|
||||||
|
import java.time.ZonedDateTime
|
||||||
import javax.persistence.Entity
|
import javax.persistence.Entity
|
||||||
import javax.persistence.EnumType
|
import javax.persistence.EnumType
|
||||||
import javax.persistence.Enumerated
|
import javax.persistence.Enumerated
|
||||||
|
|
@ -11,7 +11,7 @@ import javax.persistence.ManyToOne
|
||||||
import javax.persistence.Table
|
import javax.persistence.Table
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "user_accounts", schema = SCHEMA)
|
@Table(name = "user_accounts")
|
||||||
class UserAccountEntity: AuditableUuidIdEntity() {
|
class UserAccountEntity: AuditableUuidIdEntity() {
|
||||||
|
|
||||||
lateinit var username: String
|
lateinit var username: String
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package ru.touchin.auth.core.user.models
|
package ru.touchin.auth.core.user.models
|
||||||
|
|
||||||
import ru.touchin.auth.core.configurations.AuthCoreDatabaseConfiguration.Companion.SCHEMA
|
|
||||||
import ru.touchin.auth.core.device.models.DeviceEntity
|
import ru.touchin.auth.core.device.models.DeviceEntity
|
||||||
import ru.touchin.auth.core.scope.models.ScopeEntity
|
import ru.touchin.auth.core.scope.models.ScopeEntity
|
||||||
import ru.touchin.common.spring.jpa.models.AuditableUuidIdEntity
|
import ru.touchin.common.spring.jpa.models.AuditableUuidIdEntity
|
||||||
|
|
@ -12,8 +11,8 @@ import javax.persistence.ManyToMany
|
||||||
import javax.persistence.Table
|
import javax.persistence.Table
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "users", schema = SCHEMA)
|
@Table(name = "users")
|
||||||
class UserEntity : AuditableUuidIdEntity() {
|
class UserEntity: AuditableUuidIdEntity() {
|
||||||
|
|
||||||
var anonymous: Boolean = true
|
var anonymous: Boolean = true
|
||||||
|
|
||||||
|
|
@ -22,7 +21,6 @@ class UserEntity : AuditableUuidIdEntity() {
|
||||||
@ManyToMany
|
@ManyToMany
|
||||||
@JoinTable(
|
@JoinTable(
|
||||||
name = "devices_users",
|
name = "devices_users",
|
||||||
schema = SCHEMA,
|
|
||||||
joinColumns = [JoinColumn(name = "user_id")],
|
joinColumns = [JoinColumn(name = "user_id")],
|
||||||
inverseJoinColumns = [JoinColumn(name = "device_id")]
|
inverseJoinColumns = [JoinColumn(name = "device_id")]
|
||||||
)
|
)
|
||||||
|
|
@ -31,14 +29,9 @@ class UserEntity : AuditableUuidIdEntity() {
|
||||||
@ManyToMany
|
@ManyToMany
|
||||||
@JoinTable(
|
@JoinTable(
|
||||||
name = "users_scopes",
|
name = "users_scopes",
|
||||||
schema = SCHEMA,
|
|
||||||
joinColumns = [JoinColumn(name = "user_id")],
|
joinColumns = [JoinColumn(name = "user_id")],
|
||||||
inverseJoinColumns = [JoinColumn(name = "scope_name")]
|
inverseJoinColumns = [JoinColumn(name = "scope_name")]
|
||||||
)
|
)
|
||||||
lateinit var scopes: MutableSet<ScopeEntity>
|
lateinit var scopes: Set<ScopeEntity>
|
||||||
|
|
||||||
fun addScopes(scopes: Collection<ScopeEntity>) {
|
|
||||||
this.scopes.addAll(scopes)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -4,7 +4,6 @@ package ru.touchin.auth.core.user.repositories
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
import org.springframework.data.jpa.repository.Query
|
import org.springframework.data.jpa.repository.Query
|
||||||
import org.springframework.data.repository.findByIdOrNull
|
|
||||||
import ru.touchin.auth.core.user.dto.enums.IdentifierType
|
import ru.touchin.auth.core.user.dto.enums.IdentifierType
|
||||||
import ru.touchin.auth.core.user.exceptions.UserAccountNotFoundException
|
import ru.touchin.auth.core.user.exceptions.UserAccountNotFoundException
|
||||||
import ru.touchin.auth.core.user.models.UserAccountEntity
|
import ru.touchin.auth.core.user.models.UserAccountEntity
|
||||||
|
|
@ -19,26 +18,9 @@ interface UserAccountRepository: JpaRepository<UserAccountEntity, UUID> {
|
||||||
""")
|
""")
|
||||||
fun findByUsername(username: String, identifierType: IdentifierType): UserAccountEntity?
|
fun findByUsername(username: String, identifierType: IdentifierType): UserAccountEntity?
|
||||||
|
|
||||||
@Query("""
|
|
||||||
SELECT ua
|
|
||||||
FROM UserAccountEntity ua
|
|
||||||
WHERE ua.user.id = :userId AND identifierType = :identifierType
|
|
||||||
""")
|
|
||||||
fun findByUserId(userId: UUID, identifierType: IdentifierType): UserAccountEntity?
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fun UserAccountRepository.findByIdOrThrow(userAccountId: UUID): UserAccountEntity {
|
|
||||||
return findByIdOrNull(userAccountId)
|
|
||||||
?: throw UserAccountNotFoundException(userAccountId.toString())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun UserAccountRepository.findByUsernameOrThrow(username: String, identifierType: IdentifierType): UserAccountEntity {
|
fun UserAccountRepository.findByUsernameOrThrow(username: String, identifierType: IdentifierType): UserAccountEntity {
|
||||||
return findByUsername(username, identifierType)
|
return findByUsername(username, identifierType)
|
||||||
?: throw UserAccountNotFoundException(username)
|
?: throw UserAccountNotFoundException(username)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun UserAccountRepository.findByUserIdOrThrow(userId: UUID, identifierType: IdentifierType): UserAccountEntity {
|
|
||||||
return findByUserId(userId, identifierType)
|
|
||||||
?: throw UserAccountNotFoundException(userId.toString())
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +1,17 @@
|
||||||
package ru.touchin.auth.core.user.services
|
package ru.touchin.auth.core.user.services
|
||||||
|
|
||||||
import ru.touchin.auth.core.user.dto.User
|
import ru.touchin.auth.core.user.dto.User
|
||||||
import ru.touchin.auth.core.user.dto.UserAccount
|
|
||||||
import ru.touchin.auth.core.user.dto.enums.IdentifierType
|
import ru.touchin.auth.core.user.dto.enums.IdentifierType
|
||||||
import ru.touchin.auth.core.user.services.dto.AddUserScopes
|
|
||||||
import ru.touchin.auth.core.user.services.dto.GetUserAccount
|
|
||||||
import ru.touchin.auth.core.user.services.dto.NewAnonymousUser
|
import ru.touchin.auth.core.user.services.dto.NewAnonymousUser
|
||||||
import ru.touchin.auth.core.user.services.dto.NewUser
|
import ru.touchin.auth.core.user.services.dto.NewUser
|
||||||
import ru.touchin.auth.core.user.services.dto.UserLogin
|
import ru.touchin.auth.core.user.services.dto.UserLogin
|
||||||
import ru.touchin.auth.core.user.services.dto.UserLogout
|
|
||||||
import ru.touchin.auth.core.user.services.dto.UserSetPassword
|
|
||||||
import ru.touchin.auth.core.user.services.dto.UserUpdatePassword
|
|
||||||
|
|
||||||
interface UserCoreService {
|
interface UserCoreService {
|
||||||
|
|
||||||
fun create(newAnonymousUser: NewAnonymousUser): User
|
fun create(newAnonymousUser: NewAnonymousUser): User
|
||||||
fun create(newUser: NewUser): User
|
fun create(newUser: NewUser): User
|
||||||
fun get(username: String, identifierType: IdentifierType): User
|
fun get(username: String, identifierType: IdentifierType): User
|
||||||
fun getUserAccount(userAccount: GetUserAccount): UserAccount
|
|
||||||
fun getOrNull(username: String, identifierType: IdentifierType): User?
|
fun getOrNull(username: String, identifierType: IdentifierType): User?
|
||||||
fun login(userLogin: UserLogin): User
|
fun login(userLogin: UserLogin): User
|
||||||
fun logout(userLogout: UserLogout)
|
|
||||||
fun updatePassword(update: UserUpdatePassword)
|
|
||||||
fun setPassword(userSetPassword: UserSetPassword)
|
|
||||||
fun addScopes(addUserScopes: AddUserScopes)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
@file:Suppress("unused")
|
@file:Suppress("unused")
|
||||||
|
|
||||||
package ru.touchin.auth.core.user.services
|
package ru.touchin.auth.core.user.services
|
||||||
|
|
||||||
import org.springframework.data.repository.findByIdOrNull
|
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder
|
import org.springframework.security.crypto.password.PasswordEncoder
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import org.springframework.transaction.annotation.Transactional
|
import org.springframework.transaction.annotation.Transactional
|
||||||
|
|
@ -11,13 +9,10 @@ import ru.touchin.auth.core.device.exceptions.DeviceAlreadyLinkedException
|
||||||
import ru.touchin.auth.core.device.models.DeviceEntity
|
import ru.touchin.auth.core.device.models.DeviceEntity
|
||||||
import ru.touchin.auth.core.device.repository.DeviceRepository
|
import ru.touchin.auth.core.device.repository.DeviceRepository
|
||||||
import ru.touchin.auth.core.device.repository.findByIdWithLockOrThrow
|
import ru.touchin.auth.core.device.repository.findByIdWithLockOrThrow
|
||||||
import ru.touchin.auth.core.scope.models.ScopeEntity
|
|
||||||
import ru.touchin.auth.core.scope.models.ScopeGroupEntity
|
import ru.touchin.auth.core.scope.models.ScopeGroupEntity
|
||||||
import ru.touchin.auth.core.scope.repositories.ScopeRepository
|
import ru.touchin.auth.core.scope.repositories.ScopeRepository
|
||||||
import ru.touchin.auth.core.user.converters.UserAccountConverter.toDto
|
|
||||||
import ru.touchin.auth.core.user.converters.UserConverter.toDto
|
import ru.touchin.auth.core.user.converters.UserConverter.toDto
|
||||||
import ru.touchin.auth.core.user.dto.User
|
import ru.touchin.auth.core.user.dto.User
|
||||||
import ru.touchin.auth.core.user.dto.UserAccount
|
|
||||||
import ru.touchin.auth.core.user.dto.enums.IdentifierType
|
import ru.touchin.auth.core.user.dto.enums.IdentifierType
|
||||||
import ru.touchin.auth.core.user.exceptions.UserAccountNotFoundException
|
import ru.touchin.auth.core.user.exceptions.UserAccountNotFoundException
|
||||||
import ru.touchin.auth.core.user.exceptions.UserAlreadyRegisteredException
|
import ru.touchin.auth.core.user.exceptions.UserAlreadyRegisteredException
|
||||||
|
|
@ -26,17 +21,10 @@ import ru.touchin.auth.core.user.models.UserAccountEntity
|
||||||
import ru.touchin.auth.core.user.models.UserEntity
|
import ru.touchin.auth.core.user.models.UserEntity
|
||||||
import ru.touchin.auth.core.user.repositories.UserAccountRepository
|
import ru.touchin.auth.core.user.repositories.UserAccountRepository
|
||||||
import ru.touchin.auth.core.user.repositories.UserRepository
|
import ru.touchin.auth.core.user.repositories.UserRepository
|
||||||
import ru.touchin.auth.core.user.repositories.findByIdOrThrow
|
|
||||||
import ru.touchin.auth.core.user.repositories.findByUserIdOrThrow
|
|
||||||
import ru.touchin.auth.core.user.repositories.findByUsernameOrThrow
|
import ru.touchin.auth.core.user.repositories.findByUsernameOrThrow
|
||||||
import ru.touchin.auth.core.user.services.dto.AddUserScopes
|
|
||||||
import ru.touchin.auth.core.user.services.dto.GetUserAccount
|
|
||||||
import ru.touchin.auth.core.user.services.dto.NewAnonymousUser
|
import ru.touchin.auth.core.user.services.dto.NewAnonymousUser
|
||||||
import ru.touchin.auth.core.user.services.dto.NewUser
|
import ru.touchin.auth.core.user.services.dto.NewUser
|
||||||
import ru.touchin.auth.core.user.services.dto.UserLogin
|
import ru.touchin.auth.core.user.services.dto.UserLogin
|
||||||
import ru.touchin.auth.core.user.services.dto.UserLogout
|
|
||||||
import ru.touchin.auth.core.user.services.dto.UserSetPassword
|
|
||||||
import ru.touchin.auth.core.user.services.dto.UserUpdatePassword
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class UserCoreServiceImpl(
|
class UserCoreServiceImpl(
|
||||||
|
|
@ -58,7 +46,7 @@ class UserCoreServiceImpl(
|
||||||
val user = UserEntity().apply {
|
val user = UserEntity().apply {
|
||||||
anonymous = true
|
anonymous = true
|
||||||
devices = hashSetOf(device)
|
devices = hashSetOf(device)
|
||||||
scopes = mutableSetOf()
|
scopes = emptySet()
|
||||||
}
|
}
|
||||||
|
|
||||||
return userRepository.save(user)
|
return userRepository.save(user)
|
||||||
|
|
@ -80,7 +68,7 @@ class UserCoreServiceImpl(
|
||||||
.apply {
|
.apply {
|
||||||
anonymous = false
|
anonymous = false
|
||||||
devices = hashSetOf(device)
|
devices = hashSetOf(device)
|
||||||
scopes = defaultScopes.toMutableSet()
|
scopes = defaultScopes.toSet()
|
||||||
}
|
}
|
||||||
.also(userRepository::save)
|
.also(userRepository::save)
|
||||||
|
|
||||||
|
|
@ -112,85 +100,19 @@ class UserCoreServiceImpl(
|
||||||
|
|
||||||
val user = userAccount.user
|
val user = userAccount.user
|
||||||
.apply {
|
.apply {
|
||||||
devices = devices + device
|
devices = hashSetOf(device)
|
||||||
}
|
}
|
||||||
.also(userRepository::save)
|
.also(userRepository::save)
|
||||||
|
|
||||||
return user.toDto(device.toDto())
|
return user.toDto(device.toDto())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
|
||||||
override fun logout(userLogout: UserLogout) {
|
|
||||||
val device = deviceRepository.findByIdWithLockOrThrow(userLogout.deviceId)
|
|
||||||
|
|
||||||
resetDeviceUsers(device)
|
|
||||||
|
|
||||||
userRepository.findByIdOrThrow(userLogout.userId)
|
|
||||||
.apply {
|
|
||||||
devices = devices - device
|
|
||||||
}
|
|
||||||
.also(userRepository::save)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
override fun updatePassword(update: UserUpdatePassword) {
|
|
||||||
val userAccount = userAccountRepository.findByIdOrThrow(update.userAccountId)
|
|
||||||
|
|
||||||
if (userAccount.password != null) {
|
|
||||||
if (!passwordEncoder.matches(update.oldPassword, userAccount.password!!)) {
|
|
||||||
throw WrongPasswordException("userAccountId=${update.userAccountId}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
userAccount.apply {
|
|
||||||
password = update.newPassword?.let(passwordEncoder::encode)
|
|
||||||
}.also(userAccountRepository::save)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
override fun setPassword(userSetPassword: UserSetPassword) {
|
|
||||||
val userAccount = userAccountRepository.findByIdOrThrow(userSetPassword.userAccountId)
|
|
||||||
|
|
||||||
userAccount.apply {
|
|
||||||
password = userSetPassword.newPassword.let(passwordEncoder::encode)
|
|
||||||
}.also(userAccountRepository::save)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
override fun addScopes(addUserScopes: AddUserScopes) {
|
|
||||||
val user = userRepository.findByIdOrThrow(addUserScopes.userId)
|
|
||||||
|
|
||||||
val newScopes = addUserScopes.scopes.map { scope ->
|
|
||||||
scopeRepository.findByIdOrNull(scope)
|
|
||||||
?: createNewScope(scope)
|
|
||||||
}
|
|
||||||
|
|
||||||
user.addScopes(newScopes)
|
|
||||||
|
|
||||||
userRepository.save(user)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createNewScope(scope: String): ScopeEntity {
|
|
||||||
val newScope = ScopeEntity().apply {
|
|
||||||
name = scope
|
|
||||||
users = mutableSetOf()
|
|
||||||
}
|
|
||||||
|
|
||||||
return scopeRepository.save(newScope)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
override fun get(username: String, identifierType: IdentifierType): User {
|
override fun get(username: String, identifierType: IdentifierType): User {
|
||||||
return getOrNull(username, identifierType)
|
return getOrNull(username, identifierType)
|
||||||
?: throw UserAccountNotFoundException(username)
|
?: throw UserAccountNotFoundException(username)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
|
||||||
override fun getUserAccount(userAccount: GetUserAccount): UserAccount {
|
|
||||||
return userAccountRepository.findByUserIdOrThrow(userAccount.userId, userAccount.identifierType)
|
|
||||||
.toDto()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
override fun getOrNull(username: String, identifierType: IdentifierType): User? {
|
override fun getOrNull(username: String, identifierType: IdentifierType): User? {
|
||||||
return userAccountRepository.findByUsername(username, identifierType)
|
return userAccountRepository.findByUsername(username, identifierType)
|
||||||
|
|
@ -3,10 +3,10 @@ databaseChangeLog:
|
||||||
id: 202105011900__create_table__devices
|
id: 202105011900__create_table__devices
|
||||||
author: touchin
|
author: touchin
|
||||||
preConditions:
|
preConditions:
|
||||||
- onFail: MARK_RAN
|
onFail: MARK_RAN
|
||||||
not:
|
not:
|
||||||
tableExists:
|
tableExists:
|
||||||
tableName: devices
|
tableName: devices
|
||||||
changes:
|
changes:
|
||||||
- createTable:
|
- createTable:
|
||||||
tableName: devices
|
tableName: devices
|
||||||
|
|
@ -3,10 +3,10 @@ databaseChangeLog:
|
||||||
id: 202105052300__create_table__users
|
id: 202105052300__create_table__users
|
||||||
author: touchin
|
author: touchin
|
||||||
preConditions:
|
preConditions:
|
||||||
- onFail: MARK_RAN
|
onFail: MARK_RAN
|
||||||
not:
|
not:
|
||||||
tableExists:
|
tableExists:
|
||||||
tableName: users
|
tableName: users
|
||||||
changes:
|
changes:
|
||||||
- createTable:
|
- createTable:
|
||||||
tableName: users
|
tableName: users
|
||||||
|
|
@ -3,10 +3,10 @@ databaseChangeLog:
|
||||||
id: 202105052310__create_table__user_accounts
|
id: 202105052310__create_table__user_accounts
|
||||||
author: touchin
|
author: touchin
|
||||||
preConditions:
|
preConditions:
|
||||||
- onFail: MARK_RAN
|
onFail: MARK_RAN
|
||||||
not:
|
not:
|
||||||
tableExists:
|
tableExists:
|
||||||
tableName: user_accounts
|
tableName: user_accounts
|
||||||
changes:
|
changes:
|
||||||
- createTable:
|
- createTable:
|
||||||
tableName: user_accounts
|
tableName: user_accounts
|
||||||
|
|
@ -3,10 +3,10 @@ databaseChangeLog:
|
||||||
id: 202105052331__create_table__devices_users
|
id: 202105052331__create_table__devices_users
|
||||||
author: touchin
|
author: touchin
|
||||||
preConditions:
|
preConditions:
|
||||||
- onFail: MARK_RAN
|
onFail: MARK_RAN
|
||||||
not:
|
not:
|
||||||
tableExists:
|
tableExists:
|
||||||
tableName: devices_users
|
tableName: devices_users
|
||||||
changes:
|
changes:
|
||||||
- createTable:
|
- createTable:
|
||||||
tableName: devices_users
|
tableName: devices_users
|
||||||
|
|
@ -3,10 +3,10 @@ databaseChangeLog:
|
||||||
id: 202105081411__create_table__scopes
|
id: 202105081411__create_table__scopes
|
||||||
author: touchin
|
author: touchin
|
||||||
preConditions:
|
preConditions:
|
||||||
- onFail: MARK_RAN
|
onFail: MARK_RAN
|
||||||
not:
|
not:
|
||||||
tableExists:
|
tableExists:
|
||||||
tableName: scopes
|
tableName: scopes
|
||||||
changes:
|
changes:
|
||||||
- createTable:
|
- createTable:
|
||||||
tableName: scopes
|
tableName: scopes
|
||||||
|
|
@ -3,10 +3,10 @@ databaseChangeLog:
|
||||||
id: 202105081431__create_table__scope_groups
|
id: 202105081431__create_table__scope_groups
|
||||||
author: touchin
|
author: touchin
|
||||||
preConditions:
|
preConditions:
|
||||||
- onFail: MARK_RAN
|
onFail: MARK_RAN
|
||||||
not:
|
not:
|
||||||
tableExists:
|
tableExists:
|
||||||
tableName: scope_groups
|
tableName: scope_groups
|
||||||
changes:
|
changes:
|
||||||
- createTable:
|
- createTable:
|
||||||
tableName: scope_groups
|
tableName: scope_groups
|
||||||
|
|
@ -3,10 +3,10 @@ databaseChangeLog:
|
||||||
id: 202105081531__create_table__users_scopes
|
id: 202105081531__create_table__users_scopes
|
||||||
author: touchin
|
author: touchin
|
||||||
preConditions:
|
preConditions:
|
||||||
- onFail: MARK_RAN
|
onFail: MARK_RAN
|
||||||
not:
|
not:
|
||||||
tableExists:
|
tableExists:
|
||||||
tableName: users_scopes
|
tableName: users_scopes
|
||||||
changes:
|
changes:
|
||||||
- createTable:
|
- createTable:
|
||||||
tableName: users_scopes
|
tableName: users_scopes
|
||||||
|
|
@ -8,12 +8,12 @@ import org.springframework.beans.factory.annotation.Autowired
|
||||||
import org.springframework.dao.DataAccessException
|
import org.springframework.dao.DataAccessException
|
||||||
import ru.touchin.auth.core.device.exceptions.DeviceNotFoundException
|
import ru.touchin.auth.core.device.exceptions.DeviceNotFoundException
|
||||||
import ru.touchin.auth.core.device.models.DeviceEntity
|
import ru.touchin.auth.core.device.models.DeviceEntity
|
||||||
|
import ru.touchin.auth.core.device.dto.enums.DevicePlatform
|
||||||
import ru.touchin.auth.core.device.repository.DeviceRepository
|
import ru.touchin.auth.core.device.repository.DeviceRepository
|
||||||
import ru.touchin.auth.core.device.repository.findByIdOrThrow
|
import ru.touchin.auth.core.device.repository.findByIdOrThrow
|
||||||
import ru.touchin.auth.core.device.repository.findByIdWithLockOrThrow
|
import ru.touchin.auth.core.device.repository.findByIdWithLockOrThrow
|
||||||
import ru.touchin.auth.core.user.models.UserEntity
|
import ru.touchin.auth.core.user.models.UserEntity
|
||||||
import ru.touchin.auth.core.user.repositories.UserRepository
|
import ru.touchin.auth.core.user.repositories.UserRepository
|
||||||
import ru.touchin.common.devices.enums.DevicePlatform
|
|
||||||
import ru.touchin.common.spring.test.jpa.repository.RepositoryTest
|
import ru.touchin.common.spring.test.jpa.repository.RepositoryTest
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
@ -14,8 +14,8 @@ import org.springframework.test.context.ActiveProfiles
|
||||||
import ru.touchin.auth.core.device.dto.Device
|
import ru.touchin.auth.core.device.dto.Device
|
||||||
import ru.touchin.auth.core.device.exceptions.DeviceNotFoundException
|
import ru.touchin.auth.core.device.exceptions.DeviceNotFoundException
|
||||||
import ru.touchin.auth.core.device.models.DeviceEntity
|
import ru.touchin.auth.core.device.models.DeviceEntity
|
||||||
|
import ru.touchin.auth.core.device.dto.enums.DevicePlatform
|
||||||
import ru.touchin.auth.core.device.repository.DeviceRepository
|
import ru.touchin.auth.core.device.repository.DeviceRepository
|
||||||
import ru.touchin.common.devices.enums.DevicePlatform
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@ActiveProfiles("test")
|
@ActiveProfiles("test")
|
||||||
|
|
@ -48,7 +48,7 @@ internal class ScopeRepositoryTest {
|
||||||
val savedScope = scopeRepository.findByIdOrThrow(scope.name)
|
val savedScope = scopeRepository.findByIdOrThrow(scope.name)
|
||||||
|
|
||||||
assertTrue(
|
assertTrue(
|
||||||
ReflectionEquals(scope, "createdAt", "users").matches(savedScope)
|
ReflectionEquals(scope, "createdAt").matches(savedScope)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -9,12 +9,12 @@ import org.mockito.internal.matchers.apachecommons.ReflectionEquals
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
import org.springframework.dao.DataAccessException
|
import org.springframework.dao.DataAccessException
|
||||||
import ru.touchin.auth.core.device.models.DeviceEntity
|
import ru.touchin.auth.core.device.models.DeviceEntity
|
||||||
|
import ru.touchin.auth.core.device.dto.enums.DevicePlatform
|
||||||
import ru.touchin.auth.core.device.repository.DeviceRepository
|
import ru.touchin.auth.core.device.repository.DeviceRepository
|
||||||
import ru.touchin.auth.core.scope.models.ScopeEntity
|
import ru.touchin.auth.core.scope.models.ScopeEntity
|
||||||
import ru.touchin.auth.core.scope.repositories.ScopeRepository
|
import ru.touchin.auth.core.scope.repositories.ScopeRepository
|
||||||
import ru.touchin.auth.core.user.exceptions.UserNotFoundException
|
import ru.touchin.auth.core.user.exceptions.UserNotFoundException
|
||||||
import ru.touchin.auth.core.user.models.UserEntity
|
import ru.touchin.auth.core.user.models.UserEntity
|
||||||
import ru.touchin.common.devices.enums.DevicePlatform
|
|
||||||
import ru.touchin.common.spring.test.jpa.repository.RepositoryTest
|
import ru.touchin.common.spring.test.jpa.repository.RepositoryTest
|
||||||
import java.time.ZonedDateTime
|
import java.time.ZonedDateTime
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
@ -43,7 +43,7 @@ internal class UserRepositoryTest {
|
||||||
anonymous = false
|
anonymous = false
|
||||||
confirmedAt = ZonedDateTime.now()
|
confirmedAt = ZonedDateTime.now()
|
||||||
devices = emptySet()
|
devices = emptySet()
|
||||||
scopes = mutableSetOf()
|
scopes = emptySet()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -51,7 +51,7 @@ internal class UserRepositoryTest {
|
||||||
|
|
||||||
val savedUser = userRepository.findByIdOrThrow(user.id!!)
|
val savedUser = userRepository.findByIdOrThrow(user.id!!)
|
||||||
|
|
||||||
assertTrue(ReflectionEquals(user, "createdAt", "confirmedAt").matches(savedUser))
|
assertTrue(ReflectionEquals(user, "createdAt").matches(savedUser))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,7 +87,7 @@ internal class UserRepositoryTest {
|
||||||
|
|
||||||
val user = UserEntity()
|
val user = UserEntity()
|
||||||
.apply {
|
.apply {
|
||||||
scopes = mutableSetOf(scope)
|
scopes = setOf(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
userRepository.saveAndFlush(user)
|
userRepository.saveAndFlush(user)
|
||||||
|
|
@ -117,7 +117,7 @@ internal class UserRepositoryTest {
|
||||||
fun shouldBeErrorIfScopeNew() {
|
fun shouldBeErrorIfScopeNew() {
|
||||||
val user = UserEntity()
|
val user = UserEntity()
|
||||||
.apply {
|
.apply {
|
||||||
scopes = mutableSetOf(ScopeEntity().apply { name = "admin" })
|
scopes = setOf(ScopeEntity().apply { name = "admin" })
|
||||||
}
|
}
|
||||||
|
|
||||||
assertThrows(DataAccessException::class.java) {
|
assertThrows(DataAccessException::class.java) {
|
||||||
|
|
@ -2,7 +2,6 @@ package ru.touchin.auth.core.user.services
|
||||||
|
|
||||||
import org.junit.jupiter.api.Assertions.assertDoesNotThrow
|
import org.junit.jupiter.api.Assertions.assertDoesNotThrow
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.Assertions.assertFalse
|
|
||||||
import org.junit.jupiter.api.Assertions.assertNotNull
|
import org.junit.jupiter.api.Assertions.assertNotNull
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.junit.jupiter.api.DisplayName
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
|
@ -11,11 +10,10 @@ import org.junit.jupiter.api.assertThrows
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder
|
import org.springframework.security.crypto.password.PasswordEncoder
|
||||||
import ru.touchin.auth.core.device.dto.Device
|
import ru.touchin.auth.core.device.dto.Device
|
||||||
|
import ru.touchin.auth.core.device.dto.enums.DevicePlatform
|
||||||
import ru.touchin.auth.core.device.repository.DeviceRepository
|
import ru.touchin.auth.core.device.repository.DeviceRepository
|
||||||
import ru.touchin.auth.core.device.repository.findByIdOrThrow
|
import ru.touchin.auth.core.device.repository.findByIdOrThrow
|
||||||
import ru.touchin.auth.core.device.services.DeviceCoreService
|
import ru.touchin.auth.core.device.services.DeviceCoreService
|
||||||
import ru.touchin.auth.core.scope.dto.Scope
|
|
||||||
import ru.touchin.auth.core.scope.models.ScopeEntity
|
|
||||||
import ru.touchin.auth.core.user.dto.User
|
import ru.touchin.auth.core.user.dto.User
|
||||||
import ru.touchin.auth.core.user.dto.enums.IdentifierType
|
import ru.touchin.auth.core.user.dto.enums.IdentifierType
|
||||||
import ru.touchin.auth.core.user.exceptions.UserAlreadyRegisteredException
|
import ru.touchin.auth.core.user.exceptions.UserAlreadyRegisteredException
|
||||||
|
|
@ -23,14 +21,11 @@ import ru.touchin.auth.core.user.exceptions.WrongPasswordException
|
||||||
import ru.touchin.auth.core.user.repositories.UserAccountRepository
|
import ru.touchin.auth.core.user.repositories.UserAccountRepository
|
||||||
import ru.touchin.auth.core.user.repositories.UserRepository
|
import ru.touchin.auth.core.user.repositories.UserRepository
|
||||||
import ru.touchin.auth.core.user.repositories.findByIdOrThrow
|
import ru.touchin.auth.core.user.repositories.findByIdOrThrow
|
||||||
import ru.touchin.auth.core.user.services.dto.AddUserScopes
|
|
||||||
import ru.touchin.auth.core.user.services.dto.NewAnonymousUser
|
import ru.touchin.auth.core.user.services.dto.NewAnonymousUser
|
||||||
import ru.touchin.auth.core.user.services.dto.NewUser
|
import ru.touchin.auth.core.user.services.dto.NewUser
|
||||||
import ru.touchin.auth.core.user.services.dto.UserLogin
|
import ru.touchin.auth.core.user.services.dto.UserLogin
|
||||||
import ru.touchin.auth.core.user.services.dto.UserLogout
|
|
||||||
import ru.touchin.auth.core.user.services.dto.UserUpdatePassword
|
|
||||||
import ru.touchin.common.devices.enums.DevicePlatform
|
|
||||||
import ru.touchin.common.spring.test.jpa.repository.RepositoryTest
|
import ru.touchin.common.spring.test.jpa.repository.RepositoryTest
|
||||||
|
import java.lang.IllegalArgumentException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.persistence.EntityManager
|
import javax.persistence.EntityManager
|
||||||
|
|
||||||
|
|
@ -184,7 +179,7 @@ internal class UserCoreServiceImplSlowTest {
|
||||||
fun loginShouldBeOk() {
|
fun loginShouldBeOk() {
|
||||||
val device = createDevice()
|
val device = createDevice()
|
||||||
|
|
||||||
val regUser = createNewUser(deviceId = device.id, password = "qwerty", username = "employee")
|
val regUser = createNewUser(deviceId = device.id, password = "qwerty", username = "employee" )
|
||||||
|
|
||||||
val logingUser = userCoreService.login(
|
val logingUser = userCoreService.login(
|
||||||
UserLogin(
|
UserLogin(
|
||||||
|
|
@ -287,128 +282,4 @@ internal class UserCoreServiceImplSlowTest {
|
||||||
assertEquals(loginUserE2.id, actualDeviceE2.users.first().id!!)
|
assertEquals(loginUserE2.id, actualDeviceE2.users.first().id!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("При логауте пользователь отлинковывается от устройства")
|
|
||||||
fun usersShouldBeUnbindedAfterLogout() {
|
|
||||||
val device = createDevice()
|
|
||||||
|
|
||||||
createNewUser(deviceId = device.id, password = "qwerty", username = "employee1")
|
|
||||||
val regUserE2 = createNewUser(deviceId = device.id, password = "qwerty", username = "employee2")
|
|
||||||
|
|
||||||
val actualDeviceE2 = deviceRepository.findByIdOrThrow(deviceId = device.id)
|
|
||||||
|
|
||||||
assertEquals(1, actualDeviceE2.users.size)
|
|
||||||
assertEquals(regUserE2.id, actualDeviceE2.users.first().id!!)
|
|
||||||
|
|
||||||
userCoreService.logout(
|
|
||||||
UserLogout(
|
|
||||||
deviceId = device.id,
|
|
||||||
userId = regUserE2.id
|
|
||||||
)
|
|
||||||
).also {
|
|
||||||
entityManager.flush()
|
|
||||||
entityManager.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
val actualDevice = deviceRepository.findByIdOrThrow(deviceId = device.id)
|
|
||||||
|
|
||||||
assertTrue(actualDevice.users.isEmpty())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("Пользователь может залогиниться на нескольких устройствах")
|
|
||||||
fun usersShouldOkTwoDevicesLogin() {
|
|
||||||
val deviceD1 = createDevice()
|
|
||||||
val deviceD2 = createDevice()
|
|
||||||
|
|
||||||
val regUser = createNewUser(deviceId = deviceD1.id, password = "qwerty", username = "employee")
|
|
||||||
|
|
||||||
userCoreService.login(
|
|
||||||
UserLogin(
|
|
||||||
deviceId = deviceD2.id,
|
|
||||||
username = "employee",
|
|
||||||
password = "qwerty",
|
|
||||||
identifierType = IdentifierType.Email,
|
|
||||||
)
|
|
||||||
).also {
|
|
||||||
entityManager.flush()
|
|
||||||
entityManager.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
val actualUser = userRepository.findByIdOrThrow(regUser.id)
|
|
||||||
|
|
||||||
assertEquals(2, actualUser.devices.size)
|
|
||||||
|
|
||||||
userCoreService.logout(
|
|
||||||
UserLogout(
|
|
||||||
deviceId = deviceD1.id,
|
|
||||||
userId = regUser.id
|
|
||||||
)
|
|
||||||
).also {
|
|
||||||
entityManager.flush()
|
|
||||||
entityManager.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
val actualUser2 = userRepository.findByIdOrThrow(regUser.id)
|
|
||||||
|
|
||||||
assertEquals(1, actualUser2.devices.size)
|
|
||||||
assertEquals(deviceD2.id, actualUser2.devices.first().id!!)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("Пользователь может сменить пароль")
|
|
||||||
fun userCanChangePassword() {
|
|
||||||
val device = createDevice()
|
|
||||||
|
|
||||||
val regUser = createNewUser(deviceId = device.id, password = "qwerty", username = "employee1")
|
|
||||||
|
|
||||||
val userAccount = userAccountRepository.findByUserId(regUser.id, IdentifierType.Email)
|
|
||||||
|
|
||||||
assertNotNull(userAccount)
|
|
||||||
|
|
||||||
userCoreService.updatePassword(
|
|
||||||
UserUpdatePassword(
|
|
||||||
userAccountId = userAccount?.id!!,
|
|
||||||
oldPassword = "qwerty",
|
|
||||||
newPassword = "QWERTY1234"
|
|
||||||
)
|
|
||||||
).also {
|
|
||||||
entityManager.flush()
|
|
||||||
entityManager.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
val actualUserAccount = userAccountRepository.findByUsername("employee1", IdentifierType.Email)
|
|
||||||
|
|
||||||
assertNotNull(actualUserAccount)
|
|
||||||
assertFalse(passwordEncoder.matches(userAccount.password!!, actualUserAccount?.password!!))
|
|
||||||
assertTrue(passwordEncoder.matches("QWERTY1234", actualUserAccount.password!!))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("Пользователю можно добавить скоупы")
|
|
||||||
fun userCanAddScope() {
|
|
||||||
val device = createDevice()
|
|
||||||
|
|
||||||
val regUser = createNewUser(deviceId = device.id, password = "qwerty", username = "employee1")
|
|
||||||
|
|
||||||
val newScopes = listOf("admin", "moderator", "robot")
|
|
||||||
|
|
||||||
userCoreService.addScopes(
|
|
||||||
AddUserScopes(
|
|
||||||
userId = regUser.id,
|
|
||||||
scopes = newScopes
|
|
||||||
)
|
|
||||||
).also {
|
|
||||||
entityManager.flush()
|
|
||||||
entityManager.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
val actualUser = userRepository.findByIdOrThrow(regUser.id)
|
|
||||||
|
|
||||||
val expectedScopes: List<String> = regUser.scopes.map(Scope::name) + newScopes
|
|
||||||
|
|
||||||
assertTrue(actualUser.scopes.map(ScopeEntity::name).containsAll(expectedScopes))
|
|
||||||
assertEquals(expectedScopes.size, actualUser.scopes.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -18,7 +18,10 @@ import org.springframework.test.context.ActiveProfiles
|
||||||
import ru.touchin.auth.core.device.converters.DeviceConverter.toDto
|
import ru.touchin.auth.core.device.converters.DeviceConverter.toDto
|
||||||
import ru.touchin.auth.core.device.exceptions.DeviceAlreadyLinkedException
|
import ru.touchin.auth.core.device.exceptions.DeviceAlreadyLinkedException
|
||||||
import ru.touchin.auth.core.device.models.DeviceEntity
|
import ru.touchin.auth.core.device.models.DeviceEntity
|
||||||
|
import ru.touchin.auth.core.device.dto.enums.DevicePlatform
|
||||||
import ru.touchin.auth.core.device.repository.DeviceRepository
|
import ru.touchin.auth.core.device.repository.DeviceRepository
|
||||||
|
import ru.touchin.auth.core.policy.dto.RegistrationPolicy
|
||||||
|
import ru.touchin.auth.core.policy.services.PolicyService
|
||||||
import ru.touchin.auth.core.scope.dto.Scope
|
import ru.touchin.auth.core.scope.dto.Scope
|
||||||
import ru.touchin.auth.core.scope.models.ScopeEntity
|
import ru.touchin.auth.core.scope.models.ScopeEntity
|
||||||
import ru.touchin.auth.core.scope.repositories.ScopeRepository
|
import ru.touchin.auth.core.scope.repositories.ScopeRepository
|
||||||
|
|
@ -29,7 +32,6 @@ import ru.touchin.auth.core.user.repositories.UserAccountRepository
|
||||||
import ru.touchin.auth.core.user.repositories.UserRepository
|
import ru.touchin.auth.core.user.repositories.UserRepository
|
||||||
import ru.touchin.auth.core.user.services.dto.NewAnonymousUser
|
import ru.touchin.auth.core.user.services.dto.NewAnonymousUser
|
||||||
import ru.touchin.auth.core.user.services.dto.NewUser
|
import ru.touchin.auth.core.user.services.dto.NewUser
|
||||||
import ru.touchin.common.devices.enums.DevicePlatform
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@ActiveProfiles("test")
|
@ActiveProfiles("test")
|
||||||
|
|
@ -2,3 +2,5 @@ databaseChangeLog:
|
||||||
- changeset:
|
- changeset:
|
||||||
id: 1
|
id: 1
|
||||||
author: test
|
author: test
|
||||||
|
- includeAll:
|
||||||
|
path: classpath*:/db/changelog/auth/core
|
||||||
|
|
@ -5,9 +5,7 @@ plugins {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":security-authorization-server-core"))
|
implementation(project(":auth-core"))
|
||||||
implementation(project(":security-resource-server-custom-jwt-configuration"))
|
|
||||||
implementation(project(":security-jwt-common"))
|
|
||||||
|
|
||||||
implementation("com.auth0:java-jwt")
|
implementation("com.auth0:java-jwt")
|
||||||
implementation("org.springframework.security:spring-security-oauth2-jose")
|
implementation("org.springframework.security:spring-security-oauth2-jose")
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
@file:Suppress("unused")
|
|
||||||
|
|
||||||
package ru.touchin.auth.core.configurations
|
package ru.touchin.auth.core.configurations
|
||||||
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationPropertiesScan
|
import org.springframework.boot.context.properties.ConfigurationPropertiesScan
|
||||||
import org.springframework.context.annotation.ComponentScan
|
import org.springframework.context.annotation.ComponentScan
|
||||||
|
import org.springframework.context.annotation.Configuration
|
||||||
import org.springframework.context.annotation.Import
|
import org.springframework.context.annotation.Import
|
||||||
|
|
||||||
|
@Configuration
|
||||||
@Import(AuthCoreConfiguration::class)
|
@Import(AuthCoreConfiguration::class)
|
||||||
@ComponentScan("ru.touchin.auth.core.tokens")
|
@ComponentScan("ru.touchin.auth.core.tokens")
|
||||||
@ConfigurationPropertiesScan("ru.touchin.auth.core.tokens")
|
@ConfigurationPropertiesScan("ru.touchin.auth.core.tokens")
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
@file:Suppress("unused")
|
|
||||||
|
|
||||||
package ru.touchin.auth.core.configurations
|
package ru.touchin.auth.core.configurations
|
||||||
|
|
||||||
import org.springframework.boot.autoconfigure.domain.EntityScan
|
import org.springframework.boot.autoconfigure.domain.EntityScan
|
||||||
|
import org.springframework.context.annotation.Configuration
|
||||||
import org.springframework.context.annotation.Import
|
import org.springframework.context.annotation.Import
|
||||||
|
|
||||||
|
@Configuration
|
||||||
@Import(AuthCoreDatabaseConfiguration::class)
|
@Import(AuthCoreDatabaseConfiguration::class)
|
||||||
@EntityScan("ru.touchin.auth.core.tokens")
|
@EntityScan("ru.touchin.auth.core.tokens")
|
||||||
class AuthTokenDatabaseConfiguration
|
class AuthTokenDatabaseConfiguration
|
||||||
|
|
@ -1,33 +1,35 @@
|
||||||
package ru.touchin.auth.core.tokens.access.config
|
package ru.touchin.auth.core.tokens.access.config
|
||||||
|
|
||||||
import com.auth0.jwt.algorithms.Algorithm
|
import com.auth0.jwt.algorithms.Algorithm
|
||||||
import org.springframework.beans.factory.annotation.Qualifier
|
|
||||||
import org.springframework.context.annotation.Bean
|
import org.springframework.context.annotation.Bean
|
||||||
import org.springframework.context.annotation.Configuration
|
import org.springframework.context.annotation.Configuration
|
||||||
import org.springframework.context.annotation.Import
|
|
||||||
import ru.touchin.auth.core.tokens.access.properties.AccessTokenProperties
|
import ru.touchin.auth.core.tokens.access.properties.AccessTokenProperties
|
||||||
import ru.touchin.auth.security.jwt.configurations.JwtConfiguration
|
import ru.touchin.common.string.StringUtils.emptyString
|
||||||
import ru.touchin.auth.security.jwt.utils.JwtUtils.getKeySpec
|
|
||||||
import java.security.KeyFactory
|
import java.security.KeyFactory
|
||||||
import java.security.interfaces.RSAPrivateKey
|
import java.security.interfaces.RSAPrivateKey
|
||||||
import java.security.interfaces.RSAPublicKey
|
import java.security.interfaces.RSAPublicKey
|
||||||
import java.security.spec.PKCS8EncodedKeySpec
|
import java.security.spec.PKCS8EncodedKeySpec
|
||||||
|
import java.security.spec.X509EncodedKeySpec
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@Import(JwtConfiguration::class)
|
|
||||||
class AccessTokenBeanConfig(private val accessTokenProperties: AccessTokenProperties) {
|
class AccessTokenBeanConfig(private val accessTokenProperties: AccessTokenProperties) {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
fun accessTokenSigningAlgorithm(
|
fun accessTokenSigningAlgorithm(): Algorithm {
|
||||||
@Qualifier("accessTokenPublicKey")
|
|
||||||
accessTokenPublicKey: RSAPublicKey
|
|
||||||
): Algorithm {
|
|
||||||
return Algorithm.RSA256(
|
return Algorithm.RSA256(
|
||||||
accessTokenPublicKey,
|
accessTokenPublicKey(),
|
||||||
accessTokenPrivateKey()
|
accessTokenPrivateKey()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean("accessTokenPublicKey")
|
||||||
|
fun accessTokenPublicKey(): RSAPublicKey {
|
||||||
|
val keySpecX509 = getKeySpec(accessTokenProperties.keyPair.public, ::X509EncodedKeySpec)
|
||||||
|
|
||||||
|
return keyFactory.generatePublic(keySpecX509) as RSAPublicKey
|
||||||
|
}
|
||||||
|
|
||||||
@Bean("accessTokenPrivateKey")
|
@Bean("accessTokenPrivateKey")
|
||||||
fun accessTokenPrivateKey(): RSAPrivateKey {
|
fun accessTokenPrivateKey(): RSAPrivateKey {
|
||||||
val keySpecPKCS8 = getKeySpec(accessTokenProperties.keyPair.private, ::PKCS8EncodedKeySpec)
|
val keySpecPKCS8 = getKeySpec(accessTokenProperties.keyPair.private, ::PKCS8EncodedKeySpec)
|
||||||
|
|
@ -35,6 +37,22 @@ class AccessTokenBeanConfig(private val accessTokenProperties: AccessTokenProper
|
||||||
return keyFactory.generatePrivate(keySpecPKCS8) as RSAPrivateKey
|
return keyFactory.generatePrivate(keySpecPKCS8) as RSAPrivateKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun <T> getKeySpec(key: String, keySpecFn: (ByteArray) -> T): T {
|
||||||
|
val rawKey = getRawKey(key)
|
||||||
|
|
||||||
|
return Base64.getDecoder()
|
||||||
|
.decode(rawKey)
|
||||||
|
.let(keySpecFn)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getRawKey(key: String): String {
|
||||||
|
return key
|
||||||
|
.replace("-----BEGIN .+KEY-----".toRegex(), emptyString())
|
||||||
|
.replace("-----END .+KEY-----".toRegex(), emptyString())
|
||||||
|
.replace("\n", emptyString())
|
||||||
|
.trim()
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val keyFactory: KeyFactory = KeyFactory.getInstance("RSA")
|
val keyFactory: KeyFactory = KeyFactory.getInstance("RSA")
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
@file:Suppress("unused")
|
|
||||||
|
|
||||||
package ru.touchin.auth.core.tokens.access.exceptions
|
package ru.touchin.auth.core.tokens.access.exceptions
|
||||||
|
|
||||||
import ru.touchin.common.exceptions.CommonException
|
import ru.touchin.common.exceptions.CommonException
|
||||||
|
|
@ -3,7 +3,7 @@ package ru.touchin.auth.core.tokens.access.services
|
||||||
import ru.touchin.auth.core.tokens.access.dto.AccessToken
|
import ru.touchin.auth.core.tokens.access.dto.AccessToken
|
||||||
import ru.touchin.auth.core.tokens.access.dto.AccessTokenRequest
|
import ru.touchin.auth.core.tokens.access.dto.AccessTokenRequest
|
||||||
|
|
||||||
interface AccessTokenCoreService {
|
interface AccessTokenService {
|
||||||
|
|
||||||
fun create(accessTokenRequest: AccessTokenRequest): AccessToken
|
fun create(accessTokenRequest: AccessTokenRequest): AccessToken
|
||||||
|
|
||||||
|
|
@ -13,10 +13,10 @@ import java.time.ZoneId
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class JwtAccessTokenCoreServiceImpl(
|
class JwtAccessTokenServiceImpl(
|
||||||
private val accessTokenProperties: AccessTokenProperties,
|
private val accessTokenProperties: AccessTokenProperties,
|
||||||
private val accessTokenSigningAlgorithm: Algorithm
|
private val accessTokenSigningAlgorithm: Algorithm
|
||||||
) : AccessTokenCoreService {
|
) : AccessTokenService {
|
||||||
|
|
||||||
private fun sign(builder: JWTCreator.Builder) = builder.sign(accessTokenSigningAlgorithm)
|
private fun sign(builder: JWTCreator.Builder) = builder.sign(accessTokenSigningAlgorithm)
|
||||||
|
|
||||||
|
|
@ -6,6 +6,5 @@ import java.time.ZonedDateTime
|
||||||
data class RefreshToken(
|
data class RefreshToken(
|
||||||
val value: String,
|
val value: String,
|
||||||
val expiresAt: ZonedDateTime,
|
val expiresAt: ZonedDateTime,
|
||||||
val usedAt: ZonedDateTime?,
|
|
||||||
val user: User,
|
val user: User,
|
||||||
)
|
)
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
@file:Suppress("unused")
|
|
||||||
|
|
||||||
package ru.touchin.auth.core.tokens.refresh.models
|
package ru.touchin.auth.core.tokens.refresh.models
|
||||||
|
|
||||||
import ru.touchin.auth.core.configurations.AuthCoreDatabaseConfiguration.Companion.SCHEMA
|
|
||||||
import ru.touchin.auth.core.device.models.DeviceEntity
|
import ru.touchin.auth.core.device.models.DeviceEntity
|
||||||
import ru.touchin.auth.core.scope.models.ScopeEntity
|
import ru.touchin.auth.core.scope.models.ScopeEntity
|
||||||
import ru.touchin.auth.core.tokens.refresh.exceptions.RefreshTokenExpiredException
|
import ru.touchin.auth.core.tokens.refresh.exceptions.RefreshTokenExpiredException
|
||||||
|
|
@ -18,15 +15,13 @@ import javax.persistence.ManyToOne
|
||||||
import javax.persistence.Table
|
import javax.persistence.Table
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "refresh_tokens", schema = SCHEMA)
|
@Table(name = "refresh_tokens")
|
||||||
class RefreshTokenEntity : AuditableUuidIdEntity() {
|
class RefreshTokenEntity : AuditableUuidIdEntity() {
|
||||||
|
|
||||||
lateinit var value: String
|
lateinit var value: String
|
||||||
|
|
||||||
lateinit var expiresAt: ZonedDateTime
|
lateinit var expiresAt: ZonedDateTime
|
||||||
|
|
||||||
var usedAt: ZonedDateTime? = null
|
|
||||||
|
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
@JoinColumn(name = "user_id")
|
@JoinColumn(name = "user_id")
|
||||||
lateinit var user: UserEntity
|
lateinit var user: UserEntity
|
||||||
|
|
@ -38,14 +33,13 @@ class RefreshTokenEntity : AuditableUuidIdEntity() {
|
||||||
@ManyToMany
|
@ManyToMany
|
||||||
@JoinTable(
|
@JoinTable(
|
||||||
name = "refresh_token_scopes",
|
name = "refresh_token_scopes",
|
||||||
schema = SCHEMA,
|
|
||||||
joinColumns = [JoinColumn(name = "refresh_token_id")],
|
joinColumns = [JoinColumn(name = "refresh_token_id")],
|
||||||
inverseJoinColumns = [JoinColumn(name = "scope_name")]
|
inverseJoinColumns = [JoinColumn(name = "scope_name")]
|
||||||
)
|
)
|
||||||
lateinit var scopes: Set<ScopeEntity>
|
lateinit var scopes: Set<ScopeEntity>
|
||||||
|
|
||||||
fun validate(): RefreshTokenEntity = this.apply {
|
fun validate(): RefreshTokenEntity = this.apply {
|
||||||
if (expiresAt.isExpired() || usedAt != null) {
|
if (expiresAt.isExpired()) {
|
||||||
throw RefreshTokenExpiredException(value)
|
throw RefreshTokenExpiredException(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,10 +3,9 @@ package ru.touchin.auth.core.tokens.refresh.services
|
||||||
import ru.touchin.auth.core.tokens.refresh.dto.RefreshToken
|
import ru.touchin.auth.core.tokens.refresh.dto.RefreshToken
|
||||||
import ru.touchin.auth.core.tokens.refresh.services.dto.NewRefreshToken
|
import ru.touchin.auth.core.tokens.refresh.services.dto.NewRefreshToken
|
||||||
|
|
||||||
interface RefreshTokenCoreService {
|
interface RefreshTokenService {
|
||||||
|
|
||||||
fun get(value: String): RefreshToken
|
fun get(value: String): RefreshToken
|
||||||
fun create(token: NewRefreshToken): RefreshToken
|
fun create(token: NewRefreshToken): RefreshToken
|
||||||
fun refresh(value: String): RefreshToken
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -7,6 +7,8 @@ import ru.touchin.auth.core.device.converters.DeviceConverter.toDto
|
||||||
import ru.touchin.auth.core.device.repository.DeviceRepository
|
import ru.touchin.auth.core.device.repository.DeviceRepository
|
||||||
import ru.touchin.auth.core.scope.dto.Scope
|
import ru.touchin.auth.core.scope.dto.Scope
|
||||||
import ru.touchin.auth.core.scope.repositories.ScopeRepository
|
import ru.touchin.auth.core.scope.repositories.ScopeRepository
|
||||||
|
import ru.touchin.auth.core.user.repositories.UserRepository
|
||||||
|
import ru.touchin.auth.core.user.repositories.findByIdOrThrow
|
||||||
import ru.touchin.auth.core.tokens.refresh.dto.RefreshToken
|
import ru.touchin.auth.core.tokens.refresh.dto.RefreshToken
|
||||||
import ru.touchin.auth.core.tokens.refresh.models.RefreshTokenEntity
|
import ru.touchin.auth.core.tokens.refresh.models.RefreshTokenEntity
|
||||||
import ru.touchin.auth.core.tokens.refresh.properties.RefreshTokenProperties
|
import ru.touchin.auth.core.tokens.refresh.properties.RefreshTokenProperties
|
||||||
|
|
@ -14,29 +16,22 @@ import ru.touchin.auth.core.tokens.refresh.repositories.RefreshTokenRepository
|
||||||
import ru.touchin.auth.core.tokens.refresh.repositories.findByValueOrThrow
|
import ru.touchin.auth.core.tokens.refresh.repositories.findByValueOrThrow
|
||||||
import ru.touchin.auth.core.tokens.refresh.services.dto.NewRefreshToken
|
import ru.touchin.auth.core.tokens.refresh.services.dto.NewRefreshToken
|
||||||
import ru.touchin.auth.core.user.converters.UserConverter.toDto
|
import ru.touchin.auth.core.user.converters.UserConverter.toDto
|
||||||
import ru.touchin.auth.core.user.repositories.UserRepository
|
|
||||||
import ru.touchin.auth.core.user.repositories.findByIdOrThrow
|
|
||||||
import ru.touchin.common.byte.ByteUtils.toHex
|
|
||||||
import ru.touchin.common.random.SecureRandomStringGenerator
|
import ru.touchin.common.random.SecureRandomStringGenerator
|
||||||
import ru.touchin.common.security.hash.HashUtils
|
|
||||||
import ru.touchin.common.security.hash.HashUtils.calculateHash
|
|
||||||
import java.time.ZonedDateTime
|
import java.time.ZonedDateTime
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class RefreshTokenCoreServiceImpl(
|
class RefreshTokenServiceImpl(
|
||||||
private val refreshTokenProperties: RefreshTokenProperties,
|
private val refreshTokenProperties: RefreshTokenProperties,
|
||||||
private val refreshTokenRepository: RefreshTokenRepository,
|
private val refreshTokenRepository: RefreshTokenRepository,
|
||||||
private val userRepository: UserRepository,
|
private val userRepository: UserRepository,
|
||||||
private val deviceRepository: DeviceRepository,
|
private val deviceRepository: DeviceRepository,
|
||||||
private val scopeRepository: ScopeRepository,
|
private val scopeRepository: ScopeRepository,
|
||||||
) : RefreshTokenCoreService {
|
) : RefreshTokenService {
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
override fun get(value: String): RefreshToken {
|
override fun get(value: String): RefreshToken {
|
||||||
val valueHash = getTokenHash(value)
|
return refreshTokenRepository.findByValueOrThrow(value)
|
||||||
|
.toDto()
|
||||||
return refreshTokenRepository.findByValueOrThrow(valueHash)
|
|
||||||
.toDto(value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
|
|
@ -44,44 +39,17 @@ class RefreshTokenCoreServiceImpl(
|
||||||
val user = userRepository.findByIdOrThrow(token.userId)
|
val user = userRepository.findByIdOrThrow(token.userId)
|
||||||
val device = token.deviceId?.let(deviceRepository::findByIdOrNull)
|
val device = token.deviceId?.let(deviceRepository::findByIdOrNull)
|
||||||
val scopes = scopeRepository.findAllById(token.scopes.map(Scope::name))
|
val scopes = scopeRepository.findAllById(token.scopes.map(Scope::name))
|
||||||
val value = generateTokenValue()
|
|
||||||
|
|
||||||
val model = RefreshTokenEntity().apply {
|
val model = RefreshTokenEntity().apply {
|
||||||
expiresAt = getExpirationDate()
|
expiresAt = getExpirationDate()
|
||||||
this.value = getTokenHash(value)
|
value = generateTokenValue()
|
||||||
this.user = user
|
this.user = user
|
||||||
this.device = device
|
this.device = device
|
||||||
this.scopes = scopes.toSet()
|
this.scopes = scopes.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
return refreshTokenRepository.save(model)
|
return refreshTokenRepository.save(model)
|
||||||
.toDto(value)
|
.toDto()
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
override fun refresh(value: String): RefreshToken {
|
|
||||||
val valueHash = getTokenHash(value)
|
|
||||||
|
|
||||||
val oldToken = refreshTokenRepository.findByValueOrThrow(valueHash)
|
|
||||||
.validate()
|
|
||||||
.apply {
|
|
||||||
usedAt = ZonedDateTime.now()
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshTokenRepository.save(oldToken)
|
|
||||||
|
|
||||||
val newValue = generateTokenValue()
|
|
||||||
|
|
||||||
val model = RefreshTokenEntity().apply {
|
|
||||||
this.value = getTokenHash(newValue)
|
|
||||||
expiresAt = getExpirationDate()
|
|
||||||
user = oldToken.user
|
|
||||||
device = oldToken.device
|
|
||||||
scopes = oldToken.scopes.toSet()
|
|
||||||
}
|
|
||||||
|
|
||||||
return refreshTokenRepository.save(model)
|
|
||||||
.toDto(newValue)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getExpirationDate(): ZonedDateTime {
|
private fun getExpirationDate(): ZonedDateTime {
|
||||||
|
|
@ -94,20 +62,13 @@ class RefreshTokenCoreServiceImpl(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getTokenHash(value: String): String {
|
|
||||||
return value.calculateHash(HashUtils.HashAlgorithm.SHA256)
|
|
||||||
.toHex()
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
fun RefreshTokenEntity.toDto(): RefreshToken {
|
||||||
fun RefreshTokenEntity.toDto(rawValue: String): RefreshToken {
|
|
||||||
val device = device?.toDto()
|
val device = device?.toDto()
|
||||||
|
|
||||||
return RefreshToken(
|
return RefreshToken(
|
||||||
value = rawValue,
|
value = value,
|
||||||
expiresAt = expiresAt,
|
expiresAt = expiresAt,
|
||||||
usedAt = usedAt,
|
|
||||||
user = user.toDto(device)
|
user = user.toDto(device)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -3,10 +3,10 @@ databaseChangeLog:
|
||||||
id: 202105201851__create_table__refresh_tokens
|
id: 202105201851__create_table__refresh_tokens
|
||||||
author: touchin
|
author: touchin
|
||||||
preConditions:
|
preConditions:
|
||||||
- onFail: MARK_RAN
|
onFail: MARK_RAN
|
||||||
not:
|
not:
|
||||||
tableExists:
|
tableExists:
|
||||||
tableName: refresh_tokens
|
tableName: refresh_tokens
|
||||||
changes:
|
changes:
|
||||||
- createTable:
|
- createTable:
|
||||||
tableName: refresh_tokens
|
tableName: refresh_tokens
|
||||||
|
|
@ -3,10 +3,10 @@ databaseChangeLog:
|
||||||
id: 202105201901__create_table__refresh_token_scopes
|
id: 202105201901__create_table__refresh_token_scopes
|
||||||
author: touchin
|
author: touchin
|
||||||
preConditions:
|
preConditions:
|
||||||
- onFail: MARK_RAN
|
onFail: MARK_RAN
|
||||||
not:
|
not:
|
||||||
tableExists:
|
tableExists:
|
||||||
tableName: refresh_token_scopes
|
tableName: refresh_token_scopes
|
||||||
changes:
|
changes:
|
||||||
- createTable:
|
- createTable:
|
||||||
tableName: refresh_token_scopes
|
tableName: refresh_token_scopes
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
import io.spring.gradle.dependencymanagement.dsl.DependencyManagementExtension
|
import io.spring.gradle.dependencymanagement.dsl.DependencyManagementExtension
|
||||||
import io.gitlab.arturbosch.detekt.extensions.DetektExtension
|
|
||||||
import org.jetbrains.kotlin.cli.common.toBooleanLenient
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm")
|
kotlin("jvm")
|
||||||
id("org.springframework.boot") apply false
|
id ("org.springframework.boot") apply false
|
||||||
|
|
||||||
// IntelliJ
|
// IntelliJ
|
||||||
idea
|
idea
|
||||||
|
|
@ -17,9 +15,6 @@ plugins {
|
||||||
// A Gradle plugin that provides Maven-like dependency management and exclusions
|
// A Gradle plugin that provides Maven-like dependency management and exclusions
|
||||||
// https://docs.spring.io/dependency-management-plugin/docs/current/reference/html/
|
// https://docs.spring.io/dependency-management-plugin/docs/current/reference/html/
|
||||||
id("io.spring.dependency-management")
|
id("io.spring.dependency-management")
|
||||||
|
|
||||||
id("io.gitlab.arturbosch.detekt")
|
|
||||||
id("org.cqfn.diktat.diktat-gradle-plugin")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
|
|
@ -35,30 +30,6 @@ allprojects {
|
||||||
apply(plugin = "idea")
|
apply(plugin = "idea")
|
||||||
}
|
}
|
||||||
|
|
||||||
diktat {
|
|
||||||
inputs {
|
|
||||||
include(
|
|
||||||
"**/src/**/*.kt",
|
|
||||||
"*.kts",
|
|
||||||
"**/*.kts",
|
|
||||||
"**/src/**/*.kts",
|
|
||||||
)
|
|
||||||
exclude(
|
|
||||||
"**/build/**",
|
|
||||||
"build/**",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val tasksFileReportEnabled = project.properties["TASKS_FILE_REPORT_ENABLED"]
|
|
||||||
?.let { it.toString().toBooleanLenient() }
|
|
||||||
?: true
|
|
||||||
|
|
||||||
reporter = if (tasksFileReportEnabled) "html" else "plain"
|
|
||||||
output = if (tasksFileReportEnabled) "${project.buildDir}/reports/diktat-report.html" else String()
|
|
||||||
diktatConfigFile = file("$rootDir/diktat-analysis.yml")
|
|
||||||
ignoreFailures = true
|
|
||||||
debug = false
|
|
||||||
}
|
|
||||||
|
|
||||||
subprojects {
|
subprojects {
|
||||||
println("Enabling Kotlin JVM plugin in project ${project.name}...")
|
println("Enabling Kotlin JVM plugin in project ${project.name}...")
|
||||||
apply(plugin = "org.jetbrains.kotlin.jvm")
|
apply(plugin = "org.jetbrains.kotlin.jvm")
|
||||||
|
|
@ -69,26 +40,6 @@ subprojects {
|
||||||
println("Enabling Spring Boot Dependency Management in project ${project.name}...")
|
println("Enabling Spring Boot Dependency Management in project ${project.name}...")
|
||||||
apply(plugin = "io.spring.dependency-management")
|
apply(plugin = "io.spring.dependency-management")
|
||||||
|
|
||||||
println("Enabling Detekt support in project ${project.name}...")
|
|
||||||
apply(plugin = "io.gitlab.arturbosch.detekt")
|
|
||||||
|
|
||||||
detekt {
|
|
||||||
config = files("$rootDir/detekt-config.yml")
|
|
||||||
source = files(
|
|
||||||
DetektExtension.Companion.DEFAULT_SRC_DIR_JAVA,
|
|
||||||
DetektExtension.Companion.DEFAULT_SRC_DIR_KOTLIN,
|
|
||||||
)
|
|
||||||
reports {
|
|
||||||
txt.enabled = false
|
|
||||||
xml.enabled = false
|
|
||||||
html {
|
|
||||||
enabled = true
|
|
||||||
destination = file("${project.buildDir}/reports/kotlin-detekt-${project.name}.html")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
configure<DependencyManagementExtension> {
|
configure<DependencyManagementExtension> {
|
||||||
imports {
|
imports {
|
||||||
mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)
|
mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)
|
||||||
|
|
@ -99,17 +50,13 @@ subprojects {
|
||||||
dependency("ch.qos.logback.contrib:logback-json-classic:0.1.5")
|
dependency("ch.qos.logback.contrib:logback-json-classic:0.1.5")
|
||||||
dependency("ch.qos.logback.contrib:logback-jackson:0.1.5")
|
dependency("ch.qos.logback.contrib:logback-jackson:0.1.5")
|
||||||
|
|
||||||
dependency("org.testcontainers:testcontainers:1.18.3")
|
dependency("org.testcontainers:testcontainers:1.15.1")
|
||||||
dependency("org.testcontainers:postgresql:1.18.3")
|
dependency("org.testcontainers:postgresql:1.15.1")
|
||||||
dependency("org.testcontainers:junit-jupiter:1.18.3")
|
dependency("org.testcontainers:junit-jupiter:1.15.1")
|
||||||
dependency("org.junit.jupiter:junit-jupiter-api:5.4.2")
|
dependency("org.junit.jupiter:junit-jupiter-api:5.4.2")
|
||||||
dependency("org.junit.jupiter:junit-jupiter-params:5.4.2")
|
dependency("org.junit.jupiter:junit-jupiter-params:5.4.2")
|
||||||
dependency("org.junit.jupiter:junit-jupiter-engine:5.4.2")
|
dependency("org.junit.jupiter:junit-jupiter-engine:5.4.2")
|
||||||
|
|
||||||
dependency("com.tngtech.archunit:archunit:1.0.1")
|
|
||||||
|
|
||||||
dependency("org.liquibase:liquibase-core:4.4.0")
|
|
||||||
|
|
||||||
dependency("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0")
|
dependency("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0")
|
||||||
dependency("org.mockito:mockito-inline:3.11.0")
|
dependency("org.mockito:mockito-inline:3.11.0")
|
||||||
|
|
||||||
|
|
@ -121,19 +68,13 @@ subprojects {
|
||||||
dependency("org.locationtech.spatial4j:spatial4j:0.8")
|
dependency("org.locationtech.spatial4j:spatial4j:0.8")
|
||||||
|
|
||||||
dependency("com.auth0:java-jwt:3.10.3")
|
dependency("com.auth0:java-jwt:3.10.3")
|
||||||
|
|
||||||
dependency("software.amazon.awssdk:s3:2.10.11")
|
|
||||||
|
|
||||||
dependency("com.google.firebase:firebase-admin:9.0.0")
|
|
||||||
|
|
||||||
dependency("com.github.ua-parser:uap-java:1.5.3")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// use for @ConstructorBinding
|
// use for @ConstructorBinding
|
||||||
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
||||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
implementation ("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||||
|
|
||||||
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
|
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
plugins {
|
|
||||||
id("kotlin")
|
|
||||||
id("kotlin-spring")
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation(project(":common"))
|
|
||||||
implementation(project(":common-spring-web"))
|
|
||||||
|
|
||||||
implementation(project(":logger-spring"))
|
|
||||||
|
|
||||||
implementation("org.springframework.boot:spring-boot-starter-web")
|
|
||||||
implementation("org.springframework.boot:spring-boot-starter-webflux")
|
|
||||||
implementation("org.springframework.boot:spring-boot-starter-aop")
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
package ru.touchin.captcha.annotations
|
|
||||||
|
|
||||||
import ru.touchin.captcha.dto.enums.CaptchaScore
|
|
||||||
|
|
||||||
@Target(AnnotationTarget.FUNCTION)
|
|
||||||
annotation class Captcha(
|
|
||||||
val action: String,
|
|
||||||
val minScore: CaptchaScore = CaptchaScore.AVERAGE
|
|
||||||
)
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
package ru.touchin.captcha.aspects
|
|
||||||
|
|
||||||
import org.aspectj.lang.annotation.Aspect
|
|
||||||
import org.aspectj.lang.annotation.Before
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
|
||||||
import org.springframework.stereotype.Component
|
|
||||||
import org.springframework.web.context.request.RequestContextHolder
|
|
||||||
import org.springframework.web.context.request.ServletRequestAttributes
|
|
||||||
import ru.touchin.captcha.annotations.Captcha
|
|
||||||
import ru.touchin.captcha.exceptions.CaptchaResponseMissingException
|
|
||||||
import ru.touchin.captcha.services.CaptchaService
|
|
||||||
|
|
||||||
@Aspect
|
|
||||||
@Component
|
|
||||||
@ConditionalOnProperty(prefix = "captcha", name = ["enabled"], havingValue = "true", matchIfMissing = true)
|
|
||||||
class CaptchaSiteVerifyAspect(private val captchaService: CaptchaService) {
|
|
||||||
|
|
||||||
@Throws(Throwable::class)
|
|
||||||
@Before("@annotation(captcha)")
|
|
||||||
fun captchaSiteVerify(captcha: Captcha) {
|
|
||||||
val currentRequestAttributes = RequestContextHolder.currentRequestAttributes()
|
|
||||||
as? ServletRequestAttributes
|
|
||||||
?: throw IllegalStateException("unable to get current request attributes")
|
|
||||||
|
|
||||||
val captchaResponse = currentRequestAttributes.request.getHeader(CAPTCHA_RESPONSE_HEADER_NAME)
|
|
||||||
?: throw CaptchaResponseMissingException()
|
|
||||||
|
|
||||||
captchaService.verify(captchaResponse)
|
|
||||||
.validateAction(captcha.action)
|
|
||||||
.validateScore(captcha.minScore.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
private const val CAPTCHA_RESPONSE_HEADER_NAME = "X-Captcha-Response"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
package ru.touchin.captcha.configurations
|
|
||||||
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationPropertiesScan
|
|
||||||
import org.springframework.context.annotation.ComponentScan
|
|
||||||
|
|
||||||
@ComponentScan("ru.touchin.captcha")
|
|
||||||
@ConfigurationPropertiesScan("ru.touchin.captcha")
|
|
||||||
class CaptchaConfiguration
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
package ru.touchin.captcha.dto
|
|
||||||
|
|
||||||
import ru.touchin.captcha.exceptions.CaptchaActionMismatchException
|
|
||||||
import ru.touchin.captcha.exceptions.CaptchaScoreBelowMinimumException
|
|
||||||
|
|
||||||
data class CaptchaVerificationResult(val score: Double, val action: String) {
|
|
||||||
|
|
||||||
fun validateScore(minScore: Double): CaptchaVerificationResult {
|
|
||||||
if (score < minScore) {
|
|
||||||
throw CaptchaScoreBelowMinimumException(score)
|
|
||||||
}
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun validateAction(actionToValidate: String): CaptchaVerificationResult {
|
|
||||||
if (action != actionToValidate) {
|
|
||||||
throw CaptchaActionMismatchException(actionToValidate)
|
|
||||||
}
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
package ru.touchin.captcha.dto.enums
|
|
||||||
|
|
||||||
enum class CaptchaScore(val value: Double) {
|
|
||||||
|
|
||||||
WEAK(0.2),
|
|
||||||
AVERAGE(0.5),
|
|
||||||
STRONG(0.8)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
package ru.touchin.captcha.dto.response
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
|
||||||
import java.time.ZonedDateTime
|
|
||||||
|
|
||||||
data class CaptchaSiteVerifyResponse(
|
|
||||||
val score: Double,
|
|
||||||
val action: String,
|
|
||||||
val success: Boolean,
|
|
||||||
@JsonProperty("challenge_ts")
|
|
||||||
val challengeTs: ZonedDateTime
|
|
||||||
)
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
package ru.touchin.captcha.exceptions
|
|
||||||
|
|
||||||
import ru.touchin.common.exceptions.CommonException
|
|
||||||
|
|
||||||
class CaptchaActionMismatchException(
|
|
||||||
action: String
|
|
||||||
) : CommonException("invalid captcha action $action")
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
package ru.touchin.captcha.exceptions
|
|
||||||
|
|
||||||
import ru.touchin.common.exceptions.CommonException
|
|
||||||
|
|
||||||
class CaptchaResponseMissingException : CommonException("missing captcha response header")
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
package ru.touchin.captcha.exceptions
|
|
||||||
|
|
||||||
import ru.touchin.common.exceptions.CommonException
|
|
||||||
|
|
||||||
class CaptchaScoreBelowMinimumException(
|
|
||||||
score: Double,
|
|
||||||
) : CommonException("captcha score below minimum $score")
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
package ru.touchin.captcha.exceptions
|
|
||||||
|
|
||||||
import ru.touchin.common.exceptions.CommonException
|
|
||||||
|
|
||||||
class CaptchaSiteVerifyFailureException(
|
|
||||||
description: String?
|
|
||||||
) : CommonException(description)
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
package ru.touchin.captcha.exceptions
|
|
||||||
|
|
||||||
import ru.touchin.common.exceptions.CommonException
|
|
||||||
|
|
||||||
class CaptchaUnknownActionException(
|
|
||||||
action: String
|
|
||||||
) : CommonException("unknown captcha action $action")
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
package ru.touchin.captcha.properties
|
|
||||||
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
|
||||||
import org.springframework.boot.context.properties.ConstructorBinding
|
|
||||||
import java.net.URI
|
|
||||||
|
|
||||||
@ConstructorBinding
|
|
||||||
@ConfigurationProperties(prefix = "captcha")
|
|
||||||
data class CaptchaProperties(
|
|
||||||
val uri: URI,
|
|
||||||
val secret: String,
|
|
||||||
val actions: List<String>
|
|
||||||
)
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
package ru.touchin.captcha.services
|
|
||||||
|
|
||||||
import ru.touchin.captcha.dto.CaptchaVerificationResult
|
|
||||||
|
|
||||||
interface CaptchaService {
|
|
||||||
|
|
||||||
fun verify(response: String): CaptchaVerificationResult
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
package ru.touchin.captcha.services.impl
|
|
||||||
|
|
||||||
import org.springframework.stereotype.Service
|
|
||||||
import ru.touchin.captcha.dto.CaptchaVerificationResult
|
|
||||||
import ru.touchin.captcha.exceptions.CaptchaUnknownActionException
|
|
||||||
import ru.touchin.captcha.properties.CaptchaProperties
|
|
||||||
import ru.touchin.captcha.services.CaptchaService
|
|
||||||
import ru.touchin.captcha.webclients.CaptchaWebClient
|
|
||||||
import ru.touchin.logger.spring.annotations.AutoLogging
|
|
||||||
import ru.touchin.logger.spring.annotations.LogValue
|
|
||||||
|
|
||||||
@Service
|
|
||||||
class CaptchaServiceImpl(
|
|
||||||
private val captchaWebClient: CaptchaWebClient,
|
|
||||||
private val captchaProperties: CaptchaProperties,
|
|
||||||
) : CaptchaService {
|
|
||||||
|
|
||||||
@LogValue
|
|
||||||
@AutoLogging(tags = ["CAPTCHA", "CAPTCHA_VERIFICATION"])
|
|
||||||
override fun verify(response: String): CaptchaVerificationResult {
|
|
||||||
val siteVerifyResponse = captchaWebClient.siteVerify(response)
|
|
||||||
|
|
||||||
if (siteVerifyResponse.action !in captchaProperties.actions) {
|
|
||||||
throw CaptchaUnknownActionException(siteVerifyResponse.action)
|
|
||||||
}
|
|
||||||
|
|
||||||
return CaptchaVerificationResult(
|
|
||||||
score = siteVerifyResponse.score,
|
|
||||||
action = siteVerifyResponse.action,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
package ru.touchin.captcha.webclients
|
|
||||||
|
|
||||||
import org.springframework.http.HttpMethod
|
|
||||||
import org.springframework.http.MediaType
|
|
||||||
import org.springframework.stereotype.Component
|
|
||||||
import org.springframework.web.reactive.function.client.WebClient
|
|
||||||
import ru.touchin.captcha.properties.CaptchaProperties
|
|
||||||
import ru.touchin.captcha.dto.response.CaptchaSiteVerifyResponse
|
|
||||||
import ru.touchin.captcha.exceptions.CaptchaSiteVerifyFailureException
|
|
||||||
import ru.touchin.common.spring.web.webclient.BaseLogWebClient
|
|
||||||
import ru.touchin.common.spring.web.webclient.dto.RequestLogData
|
|
||||||
import ru.touchin.common.spring.web.webclient.logger.WebClientLogger
|
|
||||||
|
|
||||||
private const val SITE_VERIFY_PATH = "/recaptcha/api/siteverify"
|
|
||||||
|
|
||||||
@Component
|
|
||||||
class CaptchaWebClient(
|
|
||||||
webClientLogger: WebClientLogger,
|
|
||||||
webClientBuilder: WebClient.Builder,
|
|
||||||
private val captchaProperties: CaptchaProperties,
|
|
||||||
) : BaseLogWebClient(webClientLogger, webClientBuilder) {
|
|
||||||
|
|
||||||
override fun getWebClient(): WebClient {
|
|
||||||
return getWebClientBuilder(captchaProperties.uri.toString()).build()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun siteVerify(response: String): CaptchaSiteVerifyResponse {
|
|
||||||
return getWebClient().post()
|
|
||||||
.uri {
|
|
||||||
it.path(SITE_VERIFY_PATH)
|
|
||||||
it.queryParam("response", response)
|
|
||||||
it.queryParam("secret", captchaProperties.secret)
|
|
||||||
|
|
||||||
it.build()
|
|
||||||
}
|
|
||||||
.accept(MediaType.APPLICATION_JSON)
|
|
||||||
.exchange(
|
|
||||||
clazz = CaptchaSiteVerifyResponse::class.java,
|
|
||||||
requestLogData = RequestLogData(
|
|
||||||
uri = SITE_VERIFY_PATH,
|
|
||||||
logTags = listOf("CAPTCHA_SITEVERIFY"),
|
|
||||||
method = HttpMethod.POST,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.block()!!
|
|
||||||
.also(this::validateSuccess)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun validateSuccess(captchaSiteVerifyResponse: CaptchaSiteVerifyResponse) {
|
|
||||||
if (!captchaSiteVerifyResponse.success) {
|
|
||||||
throw CaptchaSiteVerifyFailureException("Captcha siteverify request did not succeed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
plugins {
|
|
||||||
id("kotlin")
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation(project(":common"))
|
|
||||||
implementation("com.tngtech.archunit:archunit")
|
|
||||||
}
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
package ru.touchin.codestyle.archunit.rules
|
|
||||||
|
|
||||||
import com.tngtech.archunit.lang.ArchRule
|
|
||||||
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes
|
|
||||||
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods
|
|
||||||
import ru.touchin.common.exceptions.CommonException
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set of rules that defines constraint for classes naming.
|
|
||||||
*/
|
|
||||||
@Suppress("unused")
|
|
||||||
object ClassNamingArchRules {
|
|
||||||
|
|
||||||
|
|
||||||
val CLASSES_WHICH_HAVE_SCHEDULED_METHODS_MUST_HAVE_SUFFIX: ArchRule = methods()
|
|
||||||
.that()
|
|
||||||
.areAnnotatedWith("org.springframework.scheduling.annotation.Scheduled")
|
|
||||||
.should().beDeclaredInClassesThat().haveSimpleNameEndingWith("Job")
|
|
||||||
.allowEmptyShould(true)
|
|
||||||
.because("Classes that use 'Scheduled' annotation must have name with 'Job' suffix")
|
|
||||||
|
|
||||||
val ANNOTATED_SERVICE_CLASSES_MUST_HAVE_SUFFIX: ArchRule = methods()
|
|
||||||
.that().areAnnotatedWith("org.springframework.stereotype.Service")
|
|
||||||
.should().beDeclaredInClassesThat().haveSimpleNameContaining("Service")
|
|
||||||
.allowEmptyShould(true)
|
|
||||||
.because("Classes that use 'Service' annotation are assignable to class with `Service` suffix")
|
|
||||||
|
|
||||||
val ANNOTATED_ENTITY_CLASSES_MUST_HAVE_SUFFIX: ArchRule = classes()
|
|
||||||
.that().areAnnotatedWith("javax.persistence.Entity")
|
|
||||||
.should().haveSimpleNameEndingWith("Entity")
|
|
||||||
.allowEmptyShould(true)
|
|
||||||
.because("Hibernate 'Entity' classes must have name with 'Entity' suffix")
|
|
||||||
|
|
||||||
val EXCEPTION_CLASSES_MUST_HAVE_SUFFIX: ArchRule = classes()
|
|
||||||
.that().areAssignableTo(Exception::class.java)
|
|
||||||
.or().areAssignableTo(CommonException::class.java)
|
|
||||||
.should().haveSimpleNameEndingWith("Exception")
|
|
||||||
.allowEmptyShould(true)
|
|
||||||
.because("'Exception' classes must have name with 'Exception' suffix")
|
|
||||||
|
|
||||||
val JPAREPOSITORY_CLASSES_MUST_HAVE_SUFFIX: ArchRule = classes()
|
|
||||||
.that().areAssignableTo("org.springframework.data.jpa.repository.JpaRepository")
|
|
||||||
.should().haveSimpleNameEndingWith("Repository")
|
|
||||||
.allowEmptyShould(true)
|
|
||||||
.because("Classes that use 'JpaRepository' must have name with 'Repository' suffix")
|
|
||||||
|
|
||||||
val WEBCLIENT_CLASSES_MUST_HAVE_SUFFIX: ArchRule = classes()
|
|
||||||
.that().haveFullyQualifiedName("org.springframework.web.reactive.valction.client.WebClient")
|
|
||||||
.should().onlyBeAccessed().byClassesThat().haveSimpleNameEndingWith("WebClient")
|
|
||||||
.allowEmptyShould(true)
|
|
||||||
.because("Classes that use Spring 'WebClient' must have name with 'WebClient' suffix")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
package ru.touchin.codestyle.archunit.rules
|
|
||||||
|
|
||||||
import com.tngtech.archunit.lang.ArchRule
|
|
||||||
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set of rules that defines constraint for classes placement.
|
|
||||||
*/
|
|
||||||
@Suppress("unused")
|
|
||||||
object ClassPackagingArchRules {
|
|
||||||
|
|
||||||
|
|
||||||
val IMPL_CLASSES_ARE_IN_CORRESPONDING_PACKAGE: ArchRule = classes()
|
|
||||||
.that().haveSimpleNameEndingWith("Impl")
|
|
||||||
.should().resideInAPackage("..impl..")
|
|
||||||
.allowEmptyShould(true)
|
|
||||||
.because("Implementations of interfaces must reside in package 'impl'")
|
|
||||||
|
|
||||||
|
|
||||||
val ENTITIES_ARE_IN_CORRESPONDING_PACKAGE: ArchRule = classes()
|
|
||||||
.that().haveSimpleNameEndingWith("Entity")
|
|
||||||
.should().resideInAPackage("..models..")
|
|
||||||
.allowEmptyShould(true)
|
|
||||||
.because("'Entity' must reside in package 'models'")
|
|
||||||
|
|
||||||
val REPOSITORIES_ARE_IN_CORRESPONDING_PACKAGE: ArchRule = classes()
|
|
||||||
.that().haveSimpleNameEndingWith("Repository")
|
|
||||||
.should().resideInAPackage("..repositories..")
|
|
||||||
.allowEmptyShould(true)
|
|
||||||
.because("'Repository' must reside in package 'repositories'")
|
|
||||||
|
|
||||||
val CORE_SERVICES_ARE_IN_CORRESPONDING_PACKAGE: ArchRule = classes()
|
|
||||||
.that().haveSimpleNameEndingWith("CoreService")
|
|
||||||
.should().resideInAPackage("..services..")
|
|
||||||
.allowEmptyShould(true)
|
|
||||||
.because("'CoreService' must reside in package 'services'")
|
|
||||||
|
|
||||||
val CONVERTERS_ARE_IN_CORRESPONDING_PACKAGE: ArchRule = classes()
|
|
||||||
.that().haveSimpleNameEndingWith("Converter")
|
|
||||||
.should().resideInAPackage("..converters..")
|
|
||||||
.allowEmptyShould(true)
|
|
||||||
.because("'Converter' must reside in package 'converters'")
|
|
||||||
|
|
||||||
val EXCEPTIONS_ARE_IN_CORRESPONDING_PACKAGE: ArchRule = classes()
|
|
||||||
.that().haveSimpleNameEndingWith("Exception")
|
|
||||||
.should().resideInAPackage("..exceptions..")
|
|
||||||
.allowEmptyShould(true)
|
|
||||||
.because("'Exception' must reside in package 'exceptions'")
|
|
||||||
|
|
||||||
val ENUM_CLASSES_ARE_IN_CORRESPONDING_PACKAGE: ArchRule = classes()
|
|
||||||
.that().areEnums()
|
|
||||||
.should().resideInAPackage("..enums..")
|
|
||||||
.allowEmptyShould(true)
|
|
||||||
.because("'Enum' must reside in package 'enums'")
|
|
||||||
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue