mirror of
https://github.com/bitwarden/android.git
synced 2025-03-16 03:08:50 +03:00
Add organization events API request (#1468)
This commit is contained in:
parent
0faa1be4e4
commit
efbb9b3a19
10 changed files with 306 additions and 0 deletions
|
@ -0,0 +1,13 @@
|
|||
package com.x8bit.bitwarden.data.platform.datasource.network.api
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.OrganizationEvent
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
|
||||
/**
|
||||
* This interface defines the API service for posting event data.
|
||||
*/
|
||||
interface EventApi {
|
||||
@POST("/collect")
|
||||
suspend fun collectOrganizationEvents(@Body events: List<OrganizationEvent>): Result<Unit>
|
||||
}
|
|
@ -10,6 +10,8 @@ import com.x8bit.bitwarden.data.platform.datasource.network.retrofit.RetrofitsIm
|
|||
import com.x8bit.bitwarden.data.platform.datasource.network.serializer.ZonedDateTimeSerializer
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.service.ConfigService
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.service.ConfigServiceImpl
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.service.EventService
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.service.EventServiceImpl
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.service.PushService
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.service.PushServiceImpl
|
||||
import dagger.Module
|
||||
|
@ -36,6 +38,14 @@ object PlatformNetworkModule {
|
|||
retrofits: Retrofits,
|
||||
): ConfigService = ConfigServiceImpl(retrofits.unauthenticatedApiRetrofit.create())
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesEventService(
|
||||
retrofits: Retrofits,
|
||||
): EventService = EventServiceImpl(
|
||||
eventApi = retrofits.authenticatedEventsRetrofit.create(),
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providePushService(
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package com.x8bit.bitwarden.data.platform.datasource.network.model
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEventType
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
/**
|
||||
* Represents an individual organization event including the type and time.
|
||||
*/
|
||||
@Serializable
|
||||
data class OrganizationEvent(
|
||||
@SerialName("type") val type: OrganizationEventType,
|
||||
@SerialName("cipherId") val cipherId: String?,
|
||||
@SerialName("date") @Contextual val date: ZonedDateTime,
|
||||
)
|
|
@ -14,6 +14,13 @@ interface Retrofits {
|
|||
*/
|
||||
val authenticatedApiRetrofit: Retrofit
|
||||
|
||||
/**
|
||||
* Allows access to "/events" calls that must be authenticated.
|
||||
*
|
||||
* The base URL can be dynamically determined via the [BaseUrlInterceptors].
|
||||
*/
|
||||
val authenticatedEventsRetrofit: Retrofit
|
||||
|
||||
/**
|
||||
* Allows access to "/api" calls that do not require authentication.
|
||||
*
|
||||
|
|
|
@ -36,6 +36,12 @@ class RetrofitsImpl(
|
|||
)
|
||||
}
|
||||
|
||||
override val authenticatedEventsRetrofit: Retrofit by lazy {
|
||||
createAuthenticatedRetrofit(
|
||||
baseUrlInterceptor = baseUrlInterceptors.eventsInterceptor,
|
||||
)
|
||||
}
|
||||
|
||||
//endregion Authenticated Retrofits
|
||||
|
||||
//region Unauthenticated Retrofits
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
package com.x8bit.bitwarden.data.platform.datasource.network.service
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.OrganizationEvent
|
||||
|
||||
/**
|
||||
* Provides an API for submitting events.
|
||||
*/
|
||||
interface EventService {
|
||||
/**
|
||||
* Attempts to submit all of the given organizations events.
|
||||
*/
|
||||
suspend fun sendOrganizationEvents(events: List<OrganizationEvent>): Result<Unit>
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package com.x8bit.bitwarden.data.platform.datasource.network.service
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.api.EventApi
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.OrganizationEvent
|
||||
|
||||
/**
|
||||
* The default implementation of the [EventService].
|
||||
*/
|
||||
class EventServiceImpl(
|
||||
private val eventApi: EventApi,
|
||||
) : EventService {
|
||||
override suspend fun sendOrganizationEvents(
|
||||
events: List<OrganizationEvent>,
|
||||
): Result<Unit> = eventApi.collectOrganizationEvents(events = events)
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
package com.x8bit.bitwarden.data.platform.manager.model
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.serializer.BaseEnumeratedIntSerializer
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Representation of events used for organization tracking.
|
||||
*/
|
||||
@Serializable(OrganizationEventTypeSerializer::class)
|
||||
enum class OrganizationEventType {
|
||||
@SerialName("1000")
|
||||
USER_LOGGED_IN,
|
||||
|
||||
@SerialName("1001")
|
||||
USER_CHANGED_PASSWORD,
|
||||
|
||||
@SerialName("1002")
|
||||
USER_UPDATED_2FA,
|
||||
|
||||
@SerialName("1003")
|
||||
USER_DISABLED_2FA,
|
||||
|
||||
@SerialName("1004")
|
||||
USER_RECOVERED_2FA,
|
||||
|
||||
@SerialName("1005")
|
||||
USER_FAILED_LOGIN,
|
||||
|
||||
@SerialName("1006")
|
||||
USER_FAILED_LOGIN_2FA,
|
||||
|
||||
@SerialName("1007")
|
||||
USER_CLIENT_EXPORTED_VAULT,
|
||||
|
||||
@SerialName("1100")
|
||||
CIPHER_CREATED,
|
||||
|
||||
@SerialName("1101")
|
||||
CIPHER_UPDATED,
|
||||
|
||||
@SerialName("1102")
|
||||
CIPHER_DELETED,
|
||||
|
||||
@SerialName("1103")
|
||||
CIPHER_ATTACHMENT_CREATED,
|
||||
|
||||
@SerialName("1104")
|
||||
CIPHER_ATTACHMENT_DELETED,
|
||||
|
||||
@SerialName("1105")
|
||||
CIPHER_SHARED,
|
||||
|
||||
@SerialName("1106")
|
||||
CIPHER_UPDATED_COLLECTIONS,
|
||||
|
||||
@SerialName("1107")
|
||||
CIPHER_CLIENT_VIEWED,
|
||||
|
||||
@SerialName("1108")
|
||||
CIPHER_CLIENT_TOGGLED_PASSWORD_VISIBLE,
|
||||
|
||||
@SerialName("1109")
|
||||
CIPHER_CLIENT_TOGGLED_HIDDEN_FIELD_VISIBLE,
|
||||
|
||||
@SerialName("1110")
|
||||
CIPHER_CLIENT_TOGGLED_CARD_CODE_VISIBLE,
|
||||
|
||||
@SerialName("1111")
|
||||
CIPHER_CLIENT_COPIED_PASSWORD,
|
||||
|
||||
@SerialName("1112")
|
||||
CIPHER_CLIENT_COPIED_HIDDEN_FIELD,
|
||||
|
||||
@SerialName("1113")
|
||||
CIPHER_CLIENT_COPIED_CARD_CODE,
|
||||
|
||||
@SerialName("1114")
|
||||
CIPHER_CLIENT_AUTO_FILLED,
|
||||
|
||||
@SerialName("1115")
|
||||
CIPHER_SOFT_DELETED,
|
||||
|
||||
@SerialName("1116")
|
||||
CIPHER_RESTORED,
|
||||
|
||||
@SerialName("1117")
|
||||
CIPHER_CLIENT_TOGGLED_CARD_NUMBER_VISIBLE,
|
||||
|
||||
@SerialName("1300")
|
||||
COLLECTION_CREATED,
|
||||
|
||||
@SerialName("1301")
|
||||
COLLECTION_UPDATED,
|
||||
|
||||
@SerialName("1302")
|
||||
COLLECTION_DELETED,
|
||||
|
||||
@SerialName("1400")
|
||||
GROUP_CREATED,
|
||||
|
||||
@SerialName("1401")
|
||||
GROUP_UPDATED,
|
||||
|
||||
@SerialName("1402")
|
||||
GROUP_DELETED,
|
||||
|
||||
@SerialName("1500")
|
||||
ORGANIZATION_USER_INVITED,
|
||||
|
||||
@SerialName("1501")
|
||||
ORGANIZATION_USER_CONFIRMED,
|
||||
|
||||
@SerialName("1502")
|
||||
ORGANIZATION_USER_UPDATED,
|
||||
|
||||
@SerialName("1503")
|
||||
ORGANIZATION_USER_REMOVED,
|
||||
|
||||
@SerialName("1504")
|
||||
ORGANIZATION_USER_UPDATED_GROUPS,
|
||||
|
||||
@SerialName("1600")
|
||||
ORGANIZATION_UPDATED,
|
||||
|
||||
@SerialName("1601")
|
||||
ORGANIZATION_PURGED_VAULT,
|
||||
}
|
||||
|
||||
@Keep
|
||||
private class OrganizationEventTypeSerializer : BaseEnumeratedIntSerializer<OrganizationEventType>(
|
||||
values = OrganizationEventType.entries.toTypedArray(),
|
||||
)
|
|
@ -101,6 +101,36 @@ class RetrofitsTest {
|
|||
assertTrue(isRefreshAuthenticatorCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `authenticatedEventsRetrofit should not invoke the RefreshAuthenticator on success`() =
|
||||
runBlocking {
|
||||
val testApi = retrofits
|
||||
.authenticatedEventsRetrofit
|
||||
.createMockRetrofit()
|
||||
.create<TestApi>()
|
||||
|
||||
server.enqueue(MockResponse().setBody("""{}"""))
|
||||
|
||||
testApi.test()
|
||||
|
||||
assertFalse(isRefreshAuthenticatorCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `authenticatedEventsRetrofit should invoke the RefreshAuthenticator on 401`() =
|
||||
runBlocking {
|
||||
val testApi = retrofits
|
||||
.authenticatedEventsRetrofit
|
||||
.createMockRetrofit()
|
||||
.create<TestApi>()
|
||||
|
||||
server.enqueue(MockResponse().setResponseCode(401).setBody("""{}"""))
|
||||
|
||||
testApi.test()
|
||||
|
||||
assertTrue(isRefreshAuthenticatorCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unauthenticatedApiRetrofit should not invoke the RefreshAuthenticator`() = runBlocking {
|
||||
val testApi = retrofits
|
||||
|
@ -133,6 +163,24 @@ class RetrofitsTest {
|
|||
assertFalse(isEventsInterceptorCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `authenticatedEventsRetrofit should invoke the correct interceptors`() = runBlocking {
|
||||
val testApi = retrofits
|
||||
.authenticatedEventsRetrofit
|
||||
.createMockRetrofit()
|
||||
.create<TestApi>()
|
||||
|
||||
server.enqueue(MockResponse().setBody("""{}"""))
|
||||
|
||||
testApi.test()
|
||||
|
||||
assertTrue(isAuthInterceptorCalled)
|
||||
assertFalse(isApiInterceptorCalled)
|
||||
assertTrue(isheadersInterceptorCalled)
|
||||
assertFalse(isIdentityInterceptorCalled)
|
||||
assertTrue(isEventsInterceptorCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unauthenticatedApiRetrofit should invoke the correct interceptors`() = runBlocking {
|
||||
val testApi = retrofits
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
package com.x8bit.bitwarden.data.platform.datasource.network.service
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.base.BaseServiceTest
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.api.EventApi
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.OrganizationEvent
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEventType
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
import retrofit2.create
|
||||
import java.time.Clock
|
||||
import java.time.Instant
|
||||
import java.time.ZoneOffset
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
class EventServiceTest : BaseServiceTest() {
|
||||
private val fixedClock: Clock = Clock.fixed(
|
||||
Instant.parse("2023-10-27T12:00:00Z"),
|
||||
ZoneOffset.UTC,
|
||||
)
|
||||
|
||||
private val eventApi: EventApi = retrofit.create()
|
||||
|
||||
private val eventService: EventService = EventServiceImpl(
|
||||
eventApi = eventApi,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `sendOrganizationEvents should return the correct response`() = runTest {
|
||||
server.enqueue(MockResponse())
|
||||
val result = eventService.sendOrganizationEvents(
|
||||
events = listOf(
|
||||
OrganizationEvent(
|
||||
type = OrganizationEventType.CIPHER_CREATED,
|
||||
cipherId = "cipher-id",
|
||||
date = ZonedDateTime.now(fixedClock),
|
||||
),
|
||||
),
|
||||
)
|
||||
assertEquals(Unit, result.getOrThrow())
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue