Compare commits
2 Commits
master
...
feature/pu
| Author | SHA1 | Date |
|---|---|---|
|
|
658409dc2e | |
|
|
42647a4923 |
40
README.md
40
README.md
|
|
@ -222,7 +222,7 @@ server.info:
|
|||
|
||||
Модуль по обеспечению интеграции с Firebase Cloud Messaging.
|
||||
1) Подключение компонентов Spring осуществляется при помощи аннотации `@EnablePushMessageProviderFcm`.
|
||||
2) Необходимо добавление конфигурации для модуля. Пример файла конфигурации в формате yaml:
|
||||
2) Необходимо добавление конфигурации для модуля с выбранным способом хранения данных для авторизации. Пример файла конфигурации в формате yaml:
|
||||
``` yaml
|
||||
push-message-provider:
|
||||
platformProviders:
|
||||
|
|
@ -231,11 +231,43 @@ push-message-provider:
|
|||
IOS:
|
||||
- FCM
|
||||
fcm:
|
||||
appName: ${appName}
|
||||
appName: # Название приложения
|
||||
auth:
|
||||
resourcePath: credentials/firebase-admin.json
|
||||
# Выбранный тип авторизации
|
||||
client:
|
||||
readTimeout: 10s
|
||||
connectionTimeout: 1s
|
||||
```
|
||||
3) По обозначенному пути `push-message-provider-fcm.auth.resourcePath` добавляется json файл с настройками и доступами из консоли Firebase.
|
||||
3) Настраивается способ предоставления авторизации для Firebase Cloud Messaging.
|
||||
|
||||
А) Токен доступа из консоли Google, добавляемый в конфигурацию настроек:
|
||||
``` yaml
|
||||
auth:
|
||||
token:
|
||||
value: testValue
|
||||
expiresAt: 2023-01-01T23:59:59 +00:00
|
||||
```
|
||||
B) Данные в файле из консоли Firebase, добавляемые в resources с обозначением пути в конфигурации настроек:
|
||||
``` yaml
|
||||
auth:
|
||||
credentialsFile:
|
||||
path: credentials/firebase-admin.json
|
||||
```
|
||||
C) Данные из файла консоли Firebase, добавляемые в конфигурацию настроек:
|
||||
``` yaml
|
||||
auth:
|
||||
credentialsData:
|
||||
type: service_account
|
||||
projectId: testProjectId
|
||||
privateKeyId: testPrivateKeyId
|
||||
privateKey: |
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALfBshaLMW2yddmAZJRNXTZzcSbwvY93Dnjj6naWgoBJoB3mOM5bcoyWwBw12A4rwecorz74OUOc6zdqX3j8hwsSyzgAUStKM5PkOvPNRKsI4eXAWU0fmb8h1jyXwftl7EzeBjEMBTpyXkgDk3wLfHN6ciCZrnQndOvS+mMl3b0hAgMBAAECgYEAmIQZByMSrITR0ewCDyFDO52HjhWEkF310hsBkNoNiOMTFZ3vCj/WjJ/W5dM+90wUTYN0KOSnytmkVUNh6K5Yekn+yRg/mBRTwwn88hU6umB8tUqoNz7AyUltAOGyQMWqAAcVgxV+mAp/Y018j69poEHgrW4qKol65/NRZyV7/J0CQQD4rCDjmxGEuA1yMzL2i8NyNl/5vvLVfLcEnVqpHbc1+KfUHZuY7iv38xpzfmErqhCxAXfQ52edq5rXmMIVSbFrAkEAvSvfSSK9XQDJl3NEyfR3BGbsoqKIYOuJAnv4OQPSODZfTNWhc11S8y914qaSWB+Iid9HoLvAIgPH5mrzPzjSowJBAJcw4FZCI+aTmOlEI8ous8gvMy8/X5lZWFUf7s0/2fKgmjmnPsE+ndEFJ6HsxturbLaR8+05pJAClARdRjN3OL0CQGoF+8gmw1ErztCmVyiFbms2MGxagesoN4r/5jg2Tw0YVENg/HMHHCWWNREJ4L2pNsJnNOL+N4oY6mHXEWwesdcCQCUYTfLYxi+Wg/5BSC7fgl/gu0mlx07AzMoMQLDOXdisV5rpxrOoT3BOLBqyccv37AZ3e2gqb8JYyNzO6C0zswQ=
|
||||
-----END PRIVATE KEY-----
|
||||
clientEmail: testClientEmail
|
||||
clientId: testClientId
|
||||
authUri: testAuthUri
|
||||
tokenUri: testTokenUri
|
||||
authProviderX509CertUrl: testAuthProviderX509CertUrl
|
||||
clientX509CertUrl: testClientX509CertUrl
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package ru.touchin.push.message.provider.fcm.configurations
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.google.auth.oauth2.AccessToken
|
||||
import com.google.auth.oauth2.GoogleCredentials
|
||||
import com.google.firebase.FirebaseApp
|
||||
import com.google.firebase.FirebaseOptions
|
||||
|
|
@ -19,9 +21,30 @@ class PushMessageProviderFcmConfiguration {
|
|||
|
||||
@Bean
|
||||
fun firebaseMessaging(
|
||||
properties: PushMessageProviderFcmProperties
|
||||
properties: PushMessageProviderFcmProperties,
|
||||
objectMapper: ObjectMapper
|
||||
): FirebaseMessaging {
|
||||
val credentials = GoogleCredentials.fromStream(ClassPathResource(properties.auth.resourcePath).inputStream)
|
||||
val credentials = when {
|
||||
properties.auth.credentialsData != null -> {
|
||||
GoogleCredentials.fromStream(
|
||||
objectMapper.writeValueAsString(properties.auth.credentialsData).byteInputStream(Charsets.UTF_8)
|
||||
)
|
||||
}
|
||||
|
||||
properties.auth.credentialsFile != null -> {
|
||||
GoogleCredentials.fromStream(
|
||||
ClassPathResource(properties.auth.credentialsFile.path).inputStream
|
||||
)
|
||||
}
|
||||
|
||||
properties.auth.token != null -> {
|
||||
GoogleCredentials.create(
|
||||
AccessToken(properties.auth.token.value, properties.auth.token.expiresAt)
|
||||
)
|
||||
}
|
||||
|
||||
else -> throw IllegalStateException("No more authorization types allowed.")
|
||||
}
|
||||
|
||||
val options: FirebaseOptions = FirebaseOptions.builder()
|
||||
.setCredentials(credentials)
|
||||
|
|
|
|||
|
|
@ -1,28 +1,64 @@
|
|||
package ru.touchin.push.message.provider.fcm.properties
|
||||
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategies
|
||||
import com.fasterxml.jackson.databind.annotation.JsonNaming
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
import org.springframework.boot.context.properties.ConstructorBinding
|
||||
import ru.touchin.push.message.provider.enums.PlatformType
|
||||
import ru.touchin.push.message.provider.enums.PushMessageProviderType
|
||||
import ru.touchin.push.message.provider.properties.PushMessageProviderProperties
|
||||
import org.springframework.util.Assert
|
||||
import java.time.Duration
|
||||
import java.util.*
|
||||
|
||||
@ConstructorBinding
|
||||
@ConfigurationProperties(prefix = "push-message-provider.fcm")
|
||||
class PushMessageProviderFcmProperties(
|
||||
val appName: String,
|
||||
val auth: Auth.Credentials,
|
||||
val client: Client
|
||||
val auth: Auth,
|
||||
val client: Client,
|
||||
) {
|
||||
|
||||
sealed interface Auth {
|
||||
data class Auth(
|
||||
val credentialsFile: CredentialsFile?,
|
||||
val credentialsData: CredentialsData?,
|
||||
val token: AccessToken?,
|
||||
) {
|
||||
|
||||
data class Credentials(
|
||||
val resourcePath: String
|
||||
) : Auth
|
||||
init {
|
||||
Assert.notNull(
|
||||
arrayOf(credentialsFile, credentialsData, token).mapNotNull { it }.firstOrNull(),
|
||||
"Authorization configuration is not provided."
|
||||
)
|
||||
|
||||
Assert.notNull(
|
||||
arrayOf(credentialsFile, credentialsData, token).mapNotNull { it }.singleOrNull(),
|
||||
"Authorization must be configured using only single provider."
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
data class CredentialsFile(
|
||||
val path: String
|
||||
)
|
||||
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class)
|
||||
data class CredentialsData(
|
||||
val type: String,
|
||||
val projectId: String,
|
||||
val privateKeyId: String,
|
||||
val privateKey: String,
|
||||
val clientEmail: String,
|
||||
val clientId: String,
|
||||
val authUri: String,
|
||||
val tokenUri: String,
|
||||
val authProviderX509CertUrl: String,
|
||||
val clientX509CertUrl: String
|
||||
)
|
||||
|
||||
data class AccessToken(
|
||||
val value: String,
|
||||
val expiresAt: Date
|
||||
)
|
||||
|
||||
data class Client(
|
||||
val readTimeout: Duration,
|
||||
val connectionTimeout: Duration
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
package ru.touchin.push.message.provider.fcm
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesBinding
|
||||
import org.springframework.core.convert.converter.Converter
|
||||
import org.springframework.stereotype.Component
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@ConfigurationPropertiesBinding
|
||||
@Component
|
||||
class AuthConverter(
|
||||
private val simpleDateFormat: SimpleDateFormat
|
||||
) : Converter<String, Date> {
|
||||
|
||||
override fun convert(source: String?): Date? {
|
||||
return source?.let(simpleDateFormat::parse)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -5,12 +5,10 @@ import com.fasterxml.jackson.annotation.PropertyAccessor
|
|||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.databind.SerializationFeature
|
||||
import org.springframework.boot.SpringBootConfiguration
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesScan
|
||||
import org.springframework.boot.test.context.TestConfiguration
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Import
|
||||
import org.springframework.test.context.ContextConfiguration
|
||||
import ru.touchin.push.message.provider.fcm.configurations.PushMessageProviderFcmConfiguration
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@TestConfiguration
|
||||
@SpringBootConfiguration
|
||||
|
|
@ -18,12 +16,18 @@ import ru.touchin.push.message.provider.fcm.configurations.PushMessageProviderFc
|
|||
class PushMessageProviderFcmTestApplication {
|
||||
|
||||
@Bean
|
||||
fun objectMapper(): ObjectMapper {
|
||||
fun objectMapper(simpleDateFormat: SimpleDateFormat): ObjectMapper {
|
||||
return ObjectMapper().apply {
|
||||
configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
|
||||
setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE)
|
||||
setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
|
||||
dateFormat = simpleDateFormat
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun simpleDateFormat(): SimpleDateFormat {
|
||||
return SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss X", Locale.getDefault())
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
package ru.touchin.push.message.provider.fcm.configurations
|
||||
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
import org.junit.jupiter.api.Test
|
||||
import ru.touchin.push.message.provider.fcm.properties.PushMessageProviderFcmProperties
|
||||
import java.lang.IllegalArgumentException
|
||||
import java.util.*
|
||||
|
||||
class PushMessageProviderFcmPropertiesAuthTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("Один тип авторизации может быть выбран")
|
||||
fun constructor_singleConfigurationShouldBeOk() {
|
||||
PushMessageProviderFcmProperties.Auth(
|
||||
credentialsFile = null,
|
||||
credentialsData = null,
|
||||
token = PushMessageProviderFcmProperties.AccessToken(
|
||||
value = "testToken",
|
||||
expiresAt = Date()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("При отсутствии типов авторизации выбрасывается исключение")
|
||||
fun constructor_configurationMustExist() {
|
||||
Assertions.assertThrows(IllegalArgumentException::class.java) {
|
||||
PushMessageProviderFcmProperties.Auth(
|
||||
credentialsFile = null,
|
||||
credentialsData = null,
|
||||
token = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("При нескольких типах авторизации выбрасывается исключение")
|
||||
fun constructor_configurationMustBeSingle() {
|
||||
Assertions.assertThrows(IllegalArgumentException::class.java) {
|
||||
PushMessageProviderFcmProperties.Auth(
|
||||
credentialsFile = PushMessageProviderFcmProperties.CredentialsFile(
|
||||
path = "testPath"
|
||||
),
|
||||
credentialsData = null,
|
||||
token = PushMessageProviderFcmProperties.AccessToken(
|
||||
value = "testToken",
|
||||
expiresAt = Date()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -7,7 +7,9 @@ push-message-provider:
|
|||
fcm:
|
||||
appName: testAppName
|
||||
auth:
|
||||
resourcePath: credentials/firebase-admin.json
|
||||
token:
|
||||
value: testValue
|
||||
expiresAt: 2023-01-01T23:59:59 +00:00
|
||||
client:
|
||||
readTimeout: 10s
|
||||
connectionTimeout: 1s
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"type": "service_account",
|
||||
"project_id": "testProjectId",
|
||||
"private_key_id": "privateKeyId",
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\nMIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALfBshaLMW2yddmAZJRNXTZzcSbwvY93Dnjj6naWgoBJoB3mOM5bcoyWwBw12A4rwecorz74OUOc6zdqX3j8hwsSyzgAUStKM5PkOvPNRKsI4eXAWU0fmb8h1jyXwftl7EzeBjEMBTpyXkgDk3wLfHN6ciCZrnQndOvS+mMl3b0hAgMBAAECgYEAmIQZByMSrITR0ewCDyFDO52HjhWEkF310hsBkNoNiOMTFZ3vCj/WjJ/W5dM+90wUTYN0KOSnytmkVUNh6K5Yekn+yRg/mBRTwwn88hU6umB8tUqoNz7AyUltAOGyQMWqAAcVgxV+mAp/Y018j69poEHgrW4qKol65/NRZyV7/J0CQQD4rCDjmxGEuA1yMzL2i8NyNl/5vvLVfLcEnVqpHbc1+KfUHZuY7iv38xpzfmErqhCxAXfQ52edq5rXmMIVSbFrAkEAvSvfSSK9XQDJl3NEyfR3BGbsoqKIYOuJAnv4OQPSODZfTNWhc11S8y914qaSWB+Iid9HoLvAIgPH5mrzPzjSowJBAJcw4FZCI+aTmOlEI8ous8gvMy8/X5lZWFUf7s0/2fKgmjmnPsE+ndEFJ6HsxturbLaR8+05pJAClARdRjN3OL0CQGoF+8gmw1ErztCmVyiFbms2MGxagesoN4r/5jg2Tw0YVENg/HMHHCWWNREJ4L2pNsJnNOL+N4oY6mHXEWwesdcCQCUYTfLYxi+Wg/5BSC7fgl/gu0mlx07AzMoMQLDOXdisV5rpxrOoT3BOLBqyccv37AZ3e2gqb8JYyNzO6C0zswQ=\n-----END PRIVATE KEY-----\n",
|
||||
"client_email": "clientEmail",
|
||||
"client_id": "clientId",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://accounts.google.com/o/oauth2/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": "clientX509CertUrl"
|
||||
}
|
||||
Loading…
Reference in New Issue