Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.bitwarden.network.interceptor.BaseUrlsProvider
import com.bitwarden.network.model.BitwardenServiceClientConfig
import com.bitwarden.network.service.ConfigService
import com.bitwarden.network.service.EventService
import com.bitwarden.network.service.FillAssistService
import com.bitwarden.network.service.PushService
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.auth.manager.AuthTokenManager
Expand All @@ -32,6 +33,12 @@ import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
object PlatformNetworkModule {

@Provides
@Singleton
fun providesFillAssistService(
bitwardenServiceClient: BitwardenServiceClient,
): FillAssistService = bitwardenServiceClient.fillAssistService

@Provides
@Singleton
fun providesConfigService(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8106,6 +8106,7 @@ class AuthRepositoryTest {
identityUrl = "mockIdentityUrl",
notificationsUrl = "mockNotificationsUrl",
ssoUrl = "mockSsoUrl",
fillAssistRulesUrl = null,
),
featureStates = emptyMap(),
communication = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class ServerCommunicationConfigRepositoryTest {
identityUrl = null,
notificationsUrl = null,
ssoUrl = null,
fillAssistRulesUrl = null,
),
featureStates = null,
communication = ConfigResponseJson.CommunicationJson(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ private val SERVER_CONFIG = ServerConfig(
identityUrl = "http://localhost:33656",
notificationsUrl = "http://localhost:61840",
ssoUrl = "http://localhost:51822",
fillAssistRulesUrl = null,
),
featureStates = mapOf(
"dummy-boolean" to JsonPrimitive(true),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ private val SERVER_CONFIG = ServerConfig(
identityUrl = "http://localhost:33656",
notificationsUrl = "http://localhost:61840",
ssoUrl = "http://localhost:51822",
fillAssistRulesUrl = null,
),
featureStates = mapOf(
"duo-redirect" to JsonPrimitive(true),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ private val SERVER_CONFIG = ServerConfig(
identityUrl = "http://localhost:33656",
notificationsUrl = "http://localhost:61840",
ssoUrl = "http://localhost:51822",
fillAssistRulesUrl = null,
),
featureStates = mapOf(
"dummy-boolean" to JsonPrimitive(true),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ private val SERVER_CONFIG = ServerConfig(
identityUrl = "http://localhost:33656",
notificationsUrl = "http://localhost:61840",
ssoUrl = "http://localhost:51822",
fillAssistRulesUrl = null,
),
featureStates = mapOf(
"duo-redirect" to JsonPrimitive(true),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ private val SERVER_CONFIG = ServerConfig(
identityUrl = "http://localhost:33656",
notificationsUrl = "http://localhost:61840",
ssoUrl = "http://localhost:51822",
fillAssistRulesUrl = null,
),
featureStates = mapOf(
"duo-redirect" to JsonPrimitive(true),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ private val SERVER_CONFIG = ServerConfig(
identityUrl = "http://localhost:33656",
notificationsUrl = "http://localhost:61840",
ssoUrl = "http://localhost:51822",
fillAssistRulesUrl = null,
),
featureStates = mapOf(
"duo-redirect" to JsonPrimitive(true),
Expand All @@ -185,6 +186,7 @@ private val CONFIG_RESPONSE_JSON = ConfigResponseJson(
identityUrl = "http://localhost:33656",
notificationsUrl = "http://localhost:61840",
ssoUrl = "http://localhost:51822",
fillAssistRulesUrl = null,
),
featureStates = mapOf(
"duo-redirect" to JsonPrimitive(true),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import com.bitwarden.network.service.DevicesService
import com.bitwarden.network.service.DigitalAssetLinkService
import com.bitwarden.network.service.DownloadService
import com.bitwarden.network.service.EventService
import com.bitwarden.network.service.FillAssistService
import com.bitwarden.network.service.FolderService
import com.bitwarden.network.service.HaveIBeenPwnedService
import com.bitwarden.network.service.IdentityService
Expand Down Expand Up @@ -107,6 +108,11 @@ interface BitwardenServiceClient {
*/
val eventService: EventService

/**
* Provides access to the Fill-Assist service.
*/
val fillAssistService: FillAssistService

/**
* Provides access to the Folder service.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import com.bitwarden.network.service.DownloadService
import com.bitwarden.network.service.DownloadServiceImpl
import com.bitwarden.network.service.EventService
import com.bitwarden.network.service.EventServiceImpl
import com.bitwarden.network.service.FillAssistService
import com.bitwarden.network.service.FillAssistServiceImpl
import com.bitwarden.network.service.FolderService
import com.bitwarden.network.service.FolderServiceImpl
import com.bitwarden.network.service.HaveIBeenPwnedService
Expand Down Expand Up @@ -155,6 +157,12 @@ internal class BitwardenServiceClientImpl(
)
}

override val fillAssistService: FillAssistService by lazy {
FillAssistServiceImpl(
api = retrofits.createStaticRetrofit().create(),
)
}

override val haveIBeenPwnedService: HaveIBeenPwnedService by lazy {
HaveIBeenPwnedServiceImpl(
api = retrofits
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.bitwarden.network.api

import com.bitwarden.network.model.FillAssistFormsJson
import com.bitwarden.network.model.FillAssistManifestJson
import com.bitwarden.network.model.NetworkResult
import retrofit2.http.GET
import retrofit2.http.Url

/**
* Defines endpoints for retrieving fill-assist targeting rules from the fill-assist service.
* Uses [Url] to support the dynamic base URL provided by server config at runtime.
*/
internal interface FillAssistApi {
/**
* Fetches the fill-assist manifest from the given [url].
*/
@GET
suspend fun getManifest(
@Url url: String,
): NetworkResult<FillAssistManifestJson>

/**
* Fetches and decodes the forms rules file from [url].
*/
@GET
suspend fun getForms(
@Url url: String,
): NetworkResult<FillAssistFormsJson>
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ data class ConfigResponseJson(

@SerialName("sso")
val ssoUrl: String?,

@SerialName("fillAssistRules")
val fillAssistRulesUrl: String?,
)

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.bitwarden.network.model

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement

/**
* Represents the fill-assist forms rules file.
*
* @property schemaVersion The semantic version string for this file (e.g. "1.0.0").
* @property hosts Map of hostname (optionally with port) to [HostEntryJson], or null if the host
* is explicitly excluded from fill-assist.
*/
@Serializable
data class FillAssistFormsJson(
@SerialName("schemaVersion")
val schemaVersion: String? = null,

@SerialName("hosts")
val hosts: Map<String, HostEntryJson?>? = null,
) {
/**
* Form descriptions and pathname-specific overrides for a single host.
*
* @property forms Site-wide fallback form descriptions.
* @property pathnames Pathname-specific overrides; a null value means that path is excluded.
*/
@Serializable
data class HostEntryJson(
@SerialName("forms")
val forms: List<FormJson>? = null,

@SerialName("pathnames")
val pathnames: Map<String, PathnameEntryJson?>? = null,
)

/**
* Form descriptions for a specific pathname.
*
* @property forms The form descriptions for this path.
*/
@Serializable
data class PathnameEntryJson(
@SerialName("forms")
val forms: List<FormJson>? = null,
)

/**
* Describes one logical form on a page.
*
* @property category The categorical purpose of this form (e.g. "account-login").
* @property container Optional CSS selectors identifying the form's container element.
* @property fields Map of field key to [JsonElement] representing a compositeSelectorArray.
* Each array element is either a CSS selector string or an array of strings for composite
* multi-input fields. Unknown fields are gracefully ignored via [ignoreUnknownKeys].
*/
@Serializable
data class FormJson(
@SerialName("category")
val category: String? = null,

@SerialName("container")
val container: List<String>? = null,

@SerialName("fields")
val fields: Map<String, JsonElement>? = null,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.bitwarden.network.model

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

/**
* Represents the fill-assist manifest returned by the fill-assist service.
*
* @property buildId The unique identifier for this build.
* @property timestamp The ISO-8601 timestamp when this build was produced.
* @property gitSha The git commit SHA for this build.
* @property maps The map data entries keyed by map type.
*/
@Serializable
data class FillAssistManifestJson(
@SerialName("buildId")
val buildId: String? = null,

@SerialName("timestamp")
val timestamp: String? = null,

@SerialName("gitSha")
val gitSha: String? = null,

@SerialName("maps")
val maps: MapsJson? = null,
) {
/**
* Container for all available maps.
*
* @property forms Map of schema version string (e.g. "v1", "v2") to [FileEntryJson].
* Using a [Map] allows new versions to appear automatically without model changes.
*/
@Serializable
data class MapsJson(
@SerialName("forms")
val forms: Map<String, FileEntryJson?>?,
)

/**
* Metadata for a single versioned file in a map.
*
* @property filename The filename to fetch (e.g. "forms.v0.json").
* @property cid The SHA-256 content hash in "sha256:<hex>" format. Used as a staleness key
* to detect when the forms file has changed on the server, avoiding unnecessary re-downloads.
* @property schema The schema filename associated with this file version.
* @property deprecated When true, this version has entered its end-of-life support window.
* Consumers should plan migration but may continue using the version until it is removed.
*/
@Serializable
data class FileEntryJson(
@SerialName("filename")
val filename: String? = null,

@SerialName("cid")
val cid: String? = null,

@SerialName("schema")
val schema: String? = null,

@SerialName("deprecated")
val deprecated: Boolean? = null,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.bitwarden.network.service

import com.bitwarden.network.model.FillAssistFormsJson
import com.bitwarden.network.model.FillAssistManifestJson

/**
* Provides access to the fill-assist targeting rules service.
*/
interface FillAssistService {
/**
* Fetches and parses the fill-assist manifest from [url].
*/
suspend fun getManifest(url: String): Result<FillAssistManifestJson>

/**
* Downloads and parses the forms rules file from [formsUrl].
*
* Returns [Result.failure] if the network request fails or parsing fails.
* Version-agnostic: any forms file URL can be passed regardless of schema version.
*/
suspend fun getForms(formsUrl: String): Result<FillAssistFormsJson>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.bitwarden.network.service

import com.bitwarden.network.api.FillAssistApi
import com.bitwarden.network.model.FillAssistFormsJson
import com.bitwarden.network.model.FillAssistManifestJson
import com.bitwarden.network.util.toResult

/**
* Default implementation of [FillAssistService].
*/
internal class FillAssistServiceImpl(
private val api: FillAssistApi,
) : FillAssistService {

override suspend fun getManifest(url: String): Result<FillAssistManifestJson> =
api.getManifest(url = url).toResult()

override suspend fun getForms(formsUrl: String): Result<FillAssistFormsJson> =
api.getForms(url = formsUrl).toResult()
}
Loading
Loading