diff --git a/README.md b/README.md index ff07fe9..8d97463 100644 --- a/README.md +++ b/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-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: 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 +``` diff --git a/push-message-provider-fcm/src/main/kotlin/ru/touchin/push/message/provider/fcm/configurations/DateConverter.kt b/push-message-provider-fcm/src/main/kotlin/ru/touchin/push/message/provider/fcm/configurations/DateConverter.kt new file mode 100644 index 0000000..4b8bb37 --- /dev/null +++ b/push-message-provider-fcm/src/main/kotlin/ru/touchin/push/message/provider/fcm/configurations/DateConverter.kt @@ -0,0 +1,21 @@ +package ru.touchin.push.message.provider.fcm.configurations + +import org.springframework.beans.factory.annotation.Qualifier +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 DateConverter( + @Qualifier("push-message-provider.fcm.auth") + private val simpleDateFormat: SimpleDateFormat +) : Converter { + + override fun convert(source: String?): Date? { + return source?.let(simpleDateFormat::parse) + } + +} diff --git a/push-message-provider-fcm/src/main/kotlin/ru/touchin/push/message/provider/fcm/configurations/PushMessageProviderFcmConfiguration.kt b/push-message-provider-fcm/src/main/kotlin/ru/touchin/push/message/provider/fcm/configurations/PushMessageProviderFcmConfiguration.kt index 04e8015..b73da71 100644 --- a/push-message-provider-fcm/src/main/kotlin/ru/touchin/push/message/provider/fcm/configurations/PushMessageProviderFcmConfiguration.kt +++ b/push-message-provider-fcm/src/main/kotlin/ru/touchin/push/message/provider/fcm/configurations/PushMessageProviderFcmConfiguration.kt @@ -1,9 +1,12 @@ 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 import com.google.firebase.messaging.FirebaseMessaging +import org.springframework.beans.factory.annotation.Qualifier import org.springframework.boot.context.properties.ConfigurationPropertiesScan import org.springframework.context.annotation.Bean import org.springframework.context.annotation.ComponentScan @@ -11,6 +14,8 @@ import org.springframework.context.annotation.Import import org.springframework.core.io.ClassPathResource import ru.touchin.push.message.provider.configurations.PushMessageProviderConfiguration import ru.touchin.push.message.provider.fcm.properties.PushMessageProviderFcmProperties +import java.text.SimpleDateFormat +import java.util.* @ComponentScan("ru.touchin.push.message.provider.fcm") @ConfigurationPropertiesScan(basePackages = ["ru.touchin.push.message.provider.fcm"]) @@ -19,9 +24,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) @@ -34,4 +60,10 @@ class PushMessageProviderFcmConfiguration { return FirebaseMessaging.getInstance(firebaseApp) } + @Bean + @Qualifier("push-message-provider.fcm.auth") + fun simpleDateFormat(): SimpleDateFormat { + return SimpleDateFormat("yyyy-MM-dd HH:mm:ss X", Locale.getDefault()) + } + } diff --git a/push-message-provider-fcm/src/main/kotlin/ru/touchin/push/message/provider/fcm/properties/PushMessageProviderFcmProperties.kt b/push-message-provider-fcm/src/main/kotlin/ru/touchin/push/message/provider/fcm/properties/PushMessageProviderFcmProperties.kt index 05755bf..7a018f0 100644 --- a/push-message-provider-fcm/src/main/kotlin/ru/touchin/push/message/provider/fcm/properties/PushMessageProviderFcmProperties.kt +++ b/push-message-provider-fcm/src/main/kotlin/ru/touchin/push/message/provider/fcm/properties/PushMessageProviderFcmProperties.kt @@ -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 diff --git a/push-message-provider-fcm/src/test/kotlin/ru/touchin/push/message/provider/fcm/PushMessageProviderFcmTestApplication.kt b/push-message-provider-fcm/src/test/kotlin/ru/touchin/push/message/provider/fcm/PushMessageProviderFcmTestApplication.kt index 89b0a4b..7c2f4e7 100644 --- a/push-message-provider-fcm/src/test/kotlin/ru/touchin/push/message/provider/fcm/PushMessageProviderFcmTestApplication.kt +++ b/push-message-provider-fcm/src/test/kotlin/ru/touchin/push/message/provider/fcm/PushMessageProviderFcmTestApplication.kt @@ -4,13 +4,11 @@ import com.fasterxml.jackson.annotation.JsonAutoDetect import com.fasterxml.jackson.annotation.PropertyAccessor import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.SerializationFeature +import org.springframework.beans.factory.annotation.Qualifier 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 @TestConfiguration @SpringBootConfiguration @@ -18,11 +16,15 @@ import ru.touchin.push.message.provider.fcm.configurations.PushMessageProviderFc class PushMessageProviderFcmTestApplication { @Bean - fun objectMapper(): ObjectMapper { + fun objectMapper( + @Qualifier("push-message-provider.fcm.auth") + 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 } } diff --git a/push-message-provider-fcm/src/test/kotlin/ru/touchin/push/message/provider/fcm/configurations/PushMessageProviderFcmPropertiesAuthTest.kt b/push-message-provider-fcm/src/test/kotlin/ru/touchin/push/message/provider/fcm/configurations/PushMessageProviderFcmPropertiesAuthTest.kt new file mode 100644 index 0000000..006aa03 --- /dev/null +++ b/push-message-provider-fcm/src/test/kotlin/ru/touchin/push/message/provider/fcm/configurations/PushMessageProviderFcmPropertiesAuthTest.kt @@ -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() + ) + ) + } + } + +} diff --git a/push-message-provider-fcm/src/test/resources/application.yml b/push-message-provider-fcm/src/test/resources/application.yml index 1d4e120..7117f6d 100644 --- a/push-message-provider-fcm/src/test/resources/application.yml +++ b/push-message-provider-fcm/src/test/resources/application.yml @@ -7,7 +7,9 @@ push-message-provider: fcm: appName: testAppName auth: - resourcePath: credentials/firebase-admin.json + token: + value: testValue + expiresAt: 2023-01-01 23:59:59 +00:00 client: readTimeout: 10s connectionTimeout: 1s diff --git a/push-message-provider-fcm/src/test/resources/credentials/firebase-admin.json b/push-message-provider-fcm/src/test/resources/credentials/firebase-admin.json deleted file mode 100644 index 2eaa59f..0000000 --- a/push-message-provider-fcm/src/test/resources/credentials/firebase-admin.json +++ /dev/null @@ -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" -}