Restructure, UniFFI, and document the fxa-client crate. (#3876)
This is a substantial refactor of the fxa-client crate, intended to bring it up to speed with our latest best-practices around developing cross-platform Rust components, in order to ease ongoing maintenance. THe core change here is that I've deleted the hand-written Kotlin and Swift bindings, replacing them with autogenerated bindings thanks to UniFFI. There is still a little bit of hand-written Kotlin, since we have a layer that automatically manages persistence via a callback. There is still a nontrivial amount of hand-written Swift, since we have a higher-level state machine built atop the lower-level fxa-client functionality (such state-machine also exists for Kotlin, but lives in the android-components repo). If UniFFI works out then we should look into moving more of that logic into shared Rust code over time. To support the introduction of UniFFI, I have restructured to Rust crate so that its public interface deliberately parallels the interface offered to Kotlin and Swift, and have moved the implementation details into a submodule named `internal`. It's my opinionated belief that structuring the crate this way will help us focus on producing a nice consumer API, which is not always the same thing as producing a nice Rust crate. On top of that, I've also revamped the documentation in the crate, leaning in to the use of `cargo doc` as the source of truth for both developer- and consumer-facing documentation. Let's consider that an experiment and see how we like it in practice. Unfortunately, this is a big PR, but I don't think I could have made it too much smaller. Hopefully it will be easier to review than its size suggests since it's mostly additions and deletions rather than complex changes.
This commit is contained in:
Родитель
ee6cb4d60d
Коммит
d77600e32c
|
@ -22,6 +22,9 @@ xcuserdata
|
|||
# Protobuf Swift
|
||||
*.pb.swift
|
||||
|
||||
# UniFFI generated artifacts
|
||||
components/**/ios/Generated
|
||||
|
||||
# Glean generated artifacts
|
||||
megazords/ios/MozillaAppServices/Generated/Metrics.swift
|
||||
megazords/ios/.venv
|
||||
|
|
|
@ -2,6 +2,15 @@
|
|||
|
||||
# Unreleased Changes
|
||||
|
||||
## FxA Client
|
||||
|
||||
### ⚠️ Breaking changes ⚠️
|
||||
|
||||
- The Kotlin and Swift bindings are now generated automatically using UniFFI.
|
||||
As a result many small details of the API surface have changed, such as some
|
||||
classes changing names to be consistent between Rust, Kotlin and Swift.
|
||||
([#3876](https://github.com/mozilla/application-services/pull/3876))
|
||||
|
||||
[Full Changelog](https://github.com/mozilla/application-services/compare/v72.1.0...main)
|
||||
|
||||
- The top-level Rust workspace now builds with Rust 1.50
|
||||
|
|
|
@ -1118,8 +1118,6 @@ dependencies = [
|
|||
"log 0.4.11",
|
||||
"mockiato",
|
||||
"mockito",
|
||||
"prost",
|
||||
"prost-derive",
|
||||
"rand_rccrypto",
|
||||
"rate-limiter",
|
||||
"rc_crypto",
|
||||
|
@ -1129,25 +1127,14 @@ dependencies = [
|
|||
"sync-guid",
|
||||
"sync15",
|
||||
"thiserror",
|
||||
"uniffi",
|
||||
"uniffi_build",
|
||||
"uniffi_macros",
|
||||
"url",
|
||||
"viaduct",
|
||||
"viaduct-reqwest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fxaclient_ffi"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ffi-support 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fxa-client",
|
||||
"lazy_static",
|
||||
"log 0.4.11",
|
||||
"prost",
|
||||
"serde_json",
|
||||
"url",
|
||||
"viaduct",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.4"
|
||||
|
@ -1219,6 +1206,12 @@ dependencies = [
|
|||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.2.7"
|
||||
|
@ -1640,7 +1633,7 @@ name = "megazord"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"autofill",
|
||||
"fxaclient_ffi",
|
||||
"fxa-client",
|
||||
"lazy_static",
|
||||
"logins_ffi",
|
||||
"nimbus-sdk",
|
||||
|
@ -1655,7 +1648,7 @@ dependencies = [
|
|||
name = "megazord_ios"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"fxaclient_ffi",
|
||||
"fxa-client",
|
||||
"glean-ffi",
|
||||
"logins_ffi",
|
||||
"places-ffi",
|
||||
|
@ -3640,6 +3633,18 @@ dependencies = [
|
|||
"uniffi_bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uniffi_macros"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10d472df0436970c100f0da4945c334e22ce78bd38ced931b3d6f1be435493e7"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.1.1"
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
members = [
|
||||
"components/autofill",
|
||||
"components/fxa-client",
|
||||
"components/fxa-client/ffi",
|
||||
"components/logins",
|
||||
"components/logins/ffi",
|
||||
"components/places",
|
||||
|
@ -80,7 +79,6 @@ exclude = [
|
|||
default-members = [
|
||||
"components/autofill",
|
||||
"components/fxa-client",
|
||||
"components/fxa-client/ffi",
|
||||
"components/logins",
|
||||
"components/logins/ffi",
|
||||
"components/places",
|
||||
|
|
|
@ -59,7 +59,8 @@ The following text applies to code linked from these dependencies:
|
|||
[jexl-parser](https://github.com/mozilla/jexl-rs),
|
||||
[uniffi](https://github.com/mozilla/uniffi-rs),
|
||||
[uniffi_bindgen](https://github.com/mozilla/uniffi-rs),
|
||||
[uniffi_build](https://github.com/mozilla/uniffi-rs)
|
||||
[uniffi_build](https://github.com/mozilla/uniffi-rs),
|
||||
[uniffi_macros](https://github.com/mozilla/uniffi-rs)
|
||||
|
||||
```
|
||||
Mozilla Public License Version 2.0
|
||||
|
@ -483,6 +484,7 @@ The following text applies to code linked from these dependencies:
|
|||
[futures-task](https://github.com/rust-lang/futures-rs),
|
||||
[futures-util](https://github.com/rust-lang/futures-rs),
|
||||
[getrandom](https://github.com/rust-random/getrandom),
|
||||
[glob](https://github.com/rust-lang/glob),
|
||||
[hashbrown](https://github.com/rust-lang/hashbrown),
|
||||
[hashlink](https://github.com/kyren/hashlink),
|
||||
[heck](https://github.com/withoutboats/heck),
|
||||
|
|
|
@ -11,8 +11,6 @@ base64 = "0.12"
|
|||
hex = "0.4"
|
||||
lazy_static = "1.4"
|
||||
log = "0.4"
|
||||
prost = "0.6"
|
||||
prost-derive = "0.6"
|
||||
rate-limiter = { path = "../support/rate-limiter" }
|
||||
rand_rccrypto = { path = "../support/rand_rccrypto" }
|
||||
serde = { version = "1", features = ["rc"] }
|
||||
|
@ -28,6 +26,11 @@ error-support = { path = "../support/error" }
|
|||
thiserror = "1.0"
|
||||
anyhow = "1.0"
|
||||
sync-guid = { path = "../support/guid", features = ["random"] }
|
||||
uniffi = "^0.7.2"
|
||||
uniffi_macros = "^0.7.2"
|
||||
|
||||
[build-dependencies]
|
||||
uniffi_build = { version = "^0.7.2", features=["builtin-bindgen"] }
|
||||
|
||||
[dev-dependencies]
|
||||
viaduct-reqwest = { path = "../support/viaduct-reqwest" }
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
# Firefox Accounts Client
|
||||
|
||||
The fxa-client component lets applications integrate with the
|
||||
[Firefox Accounts](https://mozilla.github.io/ecosystem-platform/docs/features/firefox-accounts/fxa-overview)
|
||||
identity service.
|
||||
|
||||
* [Features](#features)
|
||||
* [Using the Firefox Accounts client](#using-the-firefox-accounts-client)
|
||||
* [Working on the Firefox Accounts client](#working-on-the-firefox-accounts-client)
|
||||
|
||||
## Features
|
||||
|
||||
The fxa-client component offers support for:
|
||||
|
||||
1. Letting users sign in to your app,
|
||||
using either an email-and-password-based OAuth flow
|
||||
or a QR-code-based pairing flow.
|
||||
1. Accessing basic profile information about the signed-in user,
|
||||
such as email address and display name.
|
||||
1. Obtaining OAuth access tokens and client-side encryption keys,
|
||||
in order to access account-enabled services such as Firefox Sync.
|
||||
1. Listing and managing other applications connected to the
|
||||
user's account.
|
||||
1. Sending tabs to other applications that are capable
|
||||
of receiving them.
|
||||
1. Managing a device record for your signed-in application,
|
||||
making it visible to other applications connected to the user's
|
||||
account.
|
||||
|
||||
The component ***does not*** offer, and we have no concrete plans to offer:
|
||||
|
||||
* The ability to store data "in the cloud" associated with the user's account.
|
||||
|
||||
## Using the Firefox Accounts client
|
||||
|
||||
### Before using this component
|
||||
|
||||
This component does not currently integrate with the [Glean SDK](https://mozilla.github.io/glean/book/index.html)
|
||||
and does not submit any telemetry, so you do not need to request a data-review before using this component.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
To use this component, your application must be registered to [integrate with Firefox Accounts
|
||||
as an OAuth client](https://mozilla.github.io/ecosystem-platform/docs/process/integration-with-fxa)
|
||||
and have a unique OAuth `client_id`.
|
||||
|
||||
You should also be familiar with how to integrate application-services components
|
||||
into an application on your target platform:
|
||||
* **Android**: integrate via the
|
||||
[service-firefox-accounts](https://github.com/mozilla-mobile/android-components/tree/master/components/service/firefox-accounts/README.md)
|
||||
component from android-components, which provides higher-level conveniences for state management, persistence,
|
||||
and integration with other Android components.
|
||||
* **iOS**: start with the [guide to consuming rust components on
|
||||
iOS](https://github.com/mozilla/application-services/blob/main/docs/howtos/consuming-rust-components-on-ios.md)
|
||||
and take a look at the [higher-level Swift wrapper classes](./ios/FxAClient/).
|
||||
* **Other Platforms**: we don't know yet; please reach out on slack to discuss!
|
||||
|
||||
### Core Concepts
|
||||
|
||||
You should understand the core concepts of OAuth and the Firefox Accounts system
|
||||
before attempting to use this component. Please review the
|
||||
[Firefox Accounts Documentation](https://mozilla.github.io/ecosystem-platform/docs/features/firefox-accounts/fxa-overview)
|
||||
for more details.
|
||||
|
||||
In particular, you should understand [the different types of auth token](
|
||||
https://github.com/mozilla/ecosystem-platform/pull/39)
|
||||
in the FxA ecosystem and how each is used, as well as how [OAuth scopes](
|
||||
https://github.com/mozilla/fxa/blob/main/packages/fxa-auth-server/docs/oauth/scopes.md)
|
||||
work for accessing related services.
|
||||
|
||||
### Component API
|
||||
|
||||
For details on how to use this component, consult the [public interface definition](./src/fxa_client.udl) or view the generated Rust API documentation by running:
|
||||
|
||||
```
|
||||
cargo doc --no-deps --open
|
||||
```
|
||||
|
||||
## Working on the Firefox Accounts client
|
||||
|
||||
### Prerequisites
|
||||
|
||||
To effectively work on the FxA Client component, you will need to be familiar with:
|
||||
|
||||
* Our general [guidelines for contributors](../../docs/contributing.md).
|
||||
* The [core concepts](#core-concepts) for users of the component, outlined above.
|
||||
* The way we use [uniffi-rs](https://github.com/mozilla/uniffi-rs) to generate API wrappers for multiple languages, such as Kotlin and Swift.
|
||||
|
||||
### Overview
|
||||
|
||||
This component uses [uniffi-rs](https://github.com/mozilla/uniffi-rs) to create its
|
||||
public API surface in Rust, and then generate bindings for Kotlin and Swift. The
|
||||
code is organized as follows:
|
||||
|
||||
* The public API surface is implemented in [`./src/lib.rs`](./src/lib/rs), with matching
|
||||
declarations in [`./src/fxa_client.udl`](./src/fxa_client.udl) to define how it gets
|
||||
exposed to other languages.
|
||||
* All the implementation details are written in Rust and can be found under
|
||||
[`./src/internal/`](./src/internal).
|
||||
* The [`./android/`](./android) directory contains android-specific build scripts that
|
||||
generate Kotlin wrappers and publish them as an AAR. It also contains a small amount
|
||||
of hand-written Kotlin code for things that are not yet supposed by UniFFI.
|
||||
* The [`./ios/`](./ios) directory contains ios-specific build scripts that generate
|
||||
Swift wrappers for consumption via an XCode build. It also contains some hand-written
|
||||
Swift code to expose a higher-level convenience API.
|
||||
|
||||
### Detailed Docs
|
||||
|
||||
We use rustdoc to document both the public API of this component and its
|
||||
various internal implementation details. View the docs by running:
|
||||
|
||||
```
|
||||
cargo doc --no-deps --document-private-items --open
|
||||
```
|
||||
|
||||
In particular, the `internal` sub-module contains most of the business
|
||||
logic, and is delegated to from the public API.
|
|
@ -1,7 +1,6 @@
|
|||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'com.google.protobuf'
|
||||
|
||||
android {
|
||||
ndkVersion rootProject.ext.build.ndkVersion
|
||||
|
@ -29,12 +28,6 @@ android {
|
|||
// we can actually find it during tests. (Unfortunately, each project
|
||||
// has their own build dir)
|
||||
test.resources.srcDirs += "${project(':full-megazord').buildDir}/rustJniLibs/desktop"
|
||||
|
||||
main {
|
||||
proto {
|
||||
srcDir '../src'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is required to support new AndroidX support libraries.
|
||||
|
@ -67,20 +60,6 @@ configurations {
|
|||
// Success!
|
||||
jnaForTest
|
||||
}
|
||||
protobuf {
|
||||
protoc {
|
||||
artifact = 'com.google.protobuf:protoc:3.11.4'
|
||||
}
|
||||
generateProtoTasks {
|
||||
all().each { task ->
|
||||
task.builtins {
|
||||
java {
|
||||
option "lite"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
jnaForTest "net.java.dev.jna:jna:$jna_version@jar"
|
||||
|
@ -88,7 +67,6 @@ dependencies {
|
|||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
|
||||
implementation 'com.google.protobuf:protobuf-javalite:3.11.4'
|
||||
api project(":full-megazord")
|
||||
implementation project(":native-support")
|
||||
|
||||
|
@ -108,6 +86,20 @@ dependencies {
|
|||
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
|
||||
}
|
||||
|
||||
// We do the uniffi binding generation here:
|
||||
android.libraryVariants.all { variant ->
|
||||
def t = tasks.register("generate${variant.name.capitalize()}UniffiBindings", Exec) {
|
||||
workingDir project.rootDir
|
||||
commandLine 'cargo', 'uniffi-bindgen', 'generate', "${project.projectDir}/../src/fxa_client.udl", '--language', 'kotlin', '--out-dir', "${buildDir}/generated/source/uniffi/${variant.name}/java"
|
||||
|
||||
inputs.file '../src/fxa_client.udl'
|
||||
outputs.dir "${buildDir}/generated/source/uniffi/${variant.name}/java/"
|
||||
}
|
||||
variant.javaCompileProvider.get().dependsOn(t)
|
||||
def sourceSet = variant.sourceSets.find { it.name == variant.name }
|
||||
sourceSet.java.srcDirs += new File(buildDir, "generated/source/uniffi/${variant.name}/java")
|
||||
}
|
||||
|
||||
evaluationDependsOn(":full-megazord")
|
||||
afterEvaluate {
|
||||
// The `cargoBuild` task isn't available until after evaluation.
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package mozilla.appservices.fxaclient
|
||||
|
||||
data class AccessTokenInfo(
|
||||
val scope: String,
|
||||
val token: String,
|
||||
val key: ScopedKey?,
|
||||
val expiresAt: Long
|
||||
) {
|
||||
companion object {
|
||||
internal fun fromMessage(msg: MsgTypes.AccessTokenInfo): AccessTokenInfo {
|
||||
return AccessTokenInfo(
|
||||
scope = msg.scope,
|
||||
token = msg.token,
|
||||
key = if (msg.hasKey()) ScopedKey.fromMessage(msg.key) else null,
|
||||
expiresAt = msg.expiresAt
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class ScopedKey(
|
||||
val kty: String,
|
||||
val scope: String,
|
||||
val k: String,
|
||||
val kid: String
|
||||
) {
|
||||
companion object {
|
||||
internal fun fromMessage(msg: MsgTypes.ScopedKey): ScopedKey {
|
||||
return ScopedKey(
|
||||
kty = msg.kty,
|
||||
scope = msg.scope,
|
||||
k = msg.k,
|
||||
kid = msg.kid
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package mozilla.appservices.fxaclient
|
||||
|
||||
// https://proandroiddev.com/til-when-is-when-exhaustive-31d69f630a8b
|
||||
val <T> T.exhaustive: T
|
||||
get() = this
|
||||
|
||||
sealed class AccountEvent {
|
||||
// A tab with all its history entries (back button).
|
||||
class IncomingDeviceCommand(val command: mozilla.appservices.fxaclient.IncomingDeviceCommand) : AccountEvent()
|
||||
class ProfileUpdated : AccountEvent()
|
||||
class AccountAuthStateChanged : AccountEvent()
|
||||
class AccountDestroyed : AccountEvent()
|
||||
class DeviceConnected(val deviceName: String) : AccountEvent()
|
||||
class DeviceDisconnected(val deviceId: String, val isLocalDevice: Boolean) : AccountEvent()
|
||||
|
||||
companion object {
|
||||
private fun fromMessage(msg: MsgTypes.AccountEvent): AccountEvent {
|
||||
return when (msg.type) {
|
||||
MsgTypes.AccountEvent.AccountEventType.INCOMING_DEVICE_COMMAND -> IncomingDeviceCommand(
|
||||
command = mozilla.appservices.fxaclient.IncomingDeviceCommand.fromMessage(msg.deviceCommand)
|
||||
)
|
||||
MsgTypes.AccountEvent.AccountEventType.PROFILE_UPDATED -> ProfileUpdated()
|
||||
MsgTypes.AccountEvent.AccountEventType.ACCOUNT_AUTH_STATE_CHANGED -> AccountAuthStateChanged()
|
||||
MsgTypes.AccountEvent.AccountEventType.ACCOUNT_DESTROYED -> AccountDestroyed()
|
||||
MsgTypes.AccountEvent.AccountEventType.DEVICE_CONNECTED -> DeviceConnected(
|
||||
deviceName = msg.deviceConnectedName
|
||||
)
|
||||
MsgTypes.AccountEvent.AccountEventType.DEVICE_DISCONNECTED -> DeviceDisconnected(
|
||||
deviceId = msg.deviceDisconnectedData.deviceId,
|
||||
isLocalDevice = msg.deviceDisconnectedData.isLocalDevice
|
||||
)
|
||||
null -> throw NullPointerException("AccountEvent type cannot be null.")
|
||||
}.exhaustive
|
||||
}
|
||||
internal fun fromCollectionMessage(msg: MsgTypes.AccountEvents): Array<AccountEvent> {
|
||||
return msg.eventsList.map {
|
||||
fromMessage(it)
|
||||
}.toTypedArray()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package mozilla.appservices.fxaclient
|
||||
|
||||
data class AuthorizationParams(
|
||||
val clientId: String,
|
||||
val scopes: Array<String>,
|
||||
val state: String,
|
||||
val accessType: String = "online",
|
||||
val pkceParams: AuthorizationPKCEParams? = null,
|
||||
val keysJwk: String? = null
|
||||
) {
|
||||
fun intoMessage(): MsgTypes.AuthorizationParams {
|
||||
var b = MsgTypes.AuthorizationParams.newBuilder()
|
||||
.setClientId(this.clientId)
|
||||
.setScope(this.scopes.joinToString(" "))
|
||||
.setState(this.state)
|
||||
.setAccessType(this.accessType)
|
||||
if (this.pkceParams != null)
|
||||
b = b.setPkceParams(this.pkceParams.intoMessage())
|
||||
if (this.keysJwk != null)
|
||||
b = b.setKeysJwk(this.keysJwk)
|
||||
return b.build()
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as AuthorizationParams
|
||||
|
||||
if (clientId != other.clientId) return false
|
||||
if (!scopes.contentEquals(other.scopes)) return false
|
||||
if (state != other.state) return false
|
||||
if (accessType != other.accessType) return false
|
||||
if (pkceParams != other.pkceParams) return false
|
||||
if (keysJwk != other.keysJwk) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = clientId.hashCode()
|
||||
result = 31 * result + scopes.contentHashCode()
|
||||
result = 31 * result + state.hashCode()
|
||||
result = 31 * result + accessType.hashCode()
|
||||
result = 31 * result + (pkceParams?.hashCode() ?: 0)
|
||||
result = 31 * result + (keysJwk?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
data class AuthorizationPKCEParams(
|
||||
val codeChallenge: String,
|
||||
val codeChallengeMethod: String = "S256"
|
||||
) {
|
||||
fun intoMessage(): MsgTypes.AuthorizationPKCEParams {
|
||||
return MsgTypes.AuthorizationPKCEParams.newBuilder()
|
||||
.setCodeChallenge(this.codeChallenge)
|
||||
.setCodeChallengeMethod(this.codeChallengeMethod)
|
||||
.build()
|
||||
}
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package mozilla.appservices.fxaclient
|
||||
|
||||
data class Device(
|
||||
val id: String,
|
||||
val displayName: String,
|
||||
val deviceType: Type,
|
||||
val pushSubscription: PushSubscription?,
|
||||
val pushEndpointExpired: Boolean,
|
||||
val isCurrentDevice: Boolean,
|
||||
val lastAccessTime: Long?,
|
||||
val capabilities: List<Capability>
|
||||
) {
|
||||
enum class Capability {
|
||||
SEND_TAB;
|
||||
|
||||
companion object {
|
||||
internal fun fromMessage(msg: MsgTypes.Device.Capability): Capability {
|
||||
return when (msg) {
|
||||
MsgTypes.Device.Capability.SEND_TAB -> SEND_TAB
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class Type {
|
||||
DESKTOP,
|
||||
MOBILE,
|
||||
TABLET,
|
||||
VR,
|
||||
TV,
|
||||
UNKNOWN;
|
||||
|
||||
companion object {
|
||||
internal fun fromMessage(msg: MsgTypes.Device.Type): Type {
|
||||
return when (msg) {
|
||||
MsgTypes.Device.Type.DESKTOP -> DESKTOP
|
||||
MsgTypes.Device.Type.MOBILE -> MOBILE
|
||||
MsgTypes.Device.Type.TABLET -> TABLET
|
||||
MsgTypes.Device.Type.VR -> VR
|
||||
MsgTypes.Device.Type.TV -> TV
|
||||
else -> UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun toNumber(): Int {
|
||||
// the number resolves to values in fxa_msg_types.proto#L41
|
||||
return when (this) {
|
||||
DESKTOP -> MsgTypes.Device.Type.DESKTOP.number
|
||||
MOBILE -> MsgTypes.Device.Type.MOBILE.number
|
||||
TABLET -> MsgTypes.Device.Type.TABLET.number
|
||||
VR -> MsgTypes.Device.Type.VR.number
|
||||
TV -> MsgTypes.Device.Type.TV.number
|
||||
else -> MsgTypes.Device.Type.UNKNOWN.number
|
||||
}
|
||||
}
|
||||
}
|
||||
data class PushSubscription(
|
||||
val endpoint: String,
|
||||
val publicKey: String,
|
||||
val authKey: String
|
||||
) {
|
||||
companion object {
|
||||
internal fun fromMessage(msg: MsgTypes.Device.PushSubscription): PushSubscription {
|
||||
return PushSubscription(
|
||||
endpoint = msg.endpoint,
|
||||
publicKey = msg.publicKey,
|
||||
authKey = msg.authKey
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
companion object {
|
||||
internal fun fromMessage(msg: MsgTypes.Device): Device {
|
||||
return Device(
|
||||
id = msg.id,
|
||||
displayName = msg.displayName,
|
||||
deviceType = Type.fromMessage(msg.type),
|
||||
pushSubscription = if (msg.hasPushSubscription()) {
|
||||
PushSubscription.fromMessage(msg.pushSubscription)
|
||||
} else null,
|
||||
pushEndpointExpired = msg.pushEndpointExpired,
|
||||
isCurrentDevice = msg.isCurrentDevice,
|
||||
lastAccessTime = if (msg.hasLastAccessTime()) msg.lastAccessTime else null,
|
||||
capabilities = msg.capabilitiesList.map { Capability.fromMessage(it) }
|
||||
)
|
||||
}
|
||||
internal fun fromCollectionMessage(msg: MsgTypes.Devices): Array<Device> {
|
||||
return msg.devicesList.map {
|
||||
fromMessage(it)
|
||||
}.toTypedArray()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Set<Device.Capability>.toCollectionMessage(): MsgTypes.Capabilities {
|
||||
val builder = MsgTypes.Capabilities.newBuilder()
|
||||
this.forEach {
|
||||
when (it) {
|
||||
Device.Capability.SEND_TAB -> builder.addCapability(MsgTypes.Device.Capability.SEND_TAB)
|
||||
}.exhaustive
|
||||
}
|
||||
return builder.build()
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package mozilla.appservices.fxaclient
|
||||
|
||||
open class FxaException(message: String) : Exception(message) {
|
||||
class Unspecified(msg: String) : FxaException(msg)
|
||||
class Unauthorized(msg: String) : FxaException(msg)
|
||||
class Network(msg: String) : FxaException(msg)
|
||||
class Panic(msg: String) : FxaException(msg)
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package mozilla.appservices.fxaclient
|
||||
|
||||
data class TabHistoryEntry(
|
||||
val title: String,
|
||||
val url: String
|
||||
)
|
||||
|
||||
sealed class IncomingDeviceCommand {
|
||||
// A tab with all its history entries (back button).
|
||||
class TabReceived(val from: Device?, val entries: Array<TabHistoryEntry>) : IncomingDeviceCommand()
|
||||
|
||||
companion object {
|
||||
internal fun fromMessage(msg: MsgTypes.IncomingDeviceCommand): IncomingDeviceCommand {
|
||||
return when (msg.type) {
|
||||
MsgTypes.IncomingDeviceCommand.IncomingDeviceCommandType.TAB_RECEIVED -> {
|
||||
val data = msg.tabReceivedData
|
||||
TabReceived(
|
||||
from = if (data.hasFrom()) Device.fromMessage(data.from) else null,
|
||||
entries = data.entriesList.map {
|
||||
TabHistoryEntry(title = it.title, url = it.url)
|
||||
}.toTypedArray()
|
||||
)
|
||||
}
|
||||
null -> throw NullPointerException("IncomingDeviceCommand type cannot be null.")
|
||||
}.exhaustive
|
||||
}
|
||||
internal fun fromCollectionMessage(msg: MsgTypes.IncomingDeviceCommands): Array<IncomingDeviceCommand> {
|
||||
return msg.commandsList.map {
|
||||
fromMessage(it)
|
||||
}.toTypedArray()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package mozilla.appservices.fxaclient
|
||||
|
||||
data class IntrospectInfo(
|
||||
val active: Boolean
|
||||
) {
|
||||
companion object {
|
||||
internal fun fromMessage(msg: MsgTypes.IntrospectInfo): IntrospectInfo {
|
||||
return IntrospectInfo(
|
||||
active = msg.active
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package mozilla.appservices.fxaclient
|
||||
|
||||
data class MetricsParams(
|
||||
val flowId: String? = null,
|
||||
val flowBeginTime: Long? = null,
|
||||
val deviceId: String? = null,
|
||||
val utmSource: String? = null,
|
||||
val utmContent: String? = null,
|
||||
val utmMedium: String? = null,
|
||||
val utmTerm: String? = null,
|
||||
val utmCampaign: String? = null,
|
||||
val entrypointExperiment: String? = null,
|
||||
val entrypointVariation: String? = null
|
||||
) {
|
||||
fun intoMessage(): MsgTypes.MetricsParams {
|
||||
var metricsParamsBuilder = MsgTypes.MetricsParams.newBuilder()
|
||||
if (flowId != null) {
|
||||
metricsParamsBuilder = metricsParamsBuilder.putParameters("flow_id", flowId)
|
||||
}
|
||||
if (flowBeginTime != null) {
|
||||
metricsParamsBuilder = metricsParamsBuilder.putParameters("flow_begin_time", flowBeginTime.toString())
|
||||
}
|
||||
if (deviceId != null) {
|
||||
metricsParamsBuilder = metricsParamsBuilder.putParameters("device_id", deviceId)
|
||||
}
|
||||
if (utmSource != null) {
|
||||
metricsParamsBuilder = metricsParamsBuilder.putParameters("utm_source", utmSource)
|
||||
}
|
||||
if (utmContent != null) {
|
||||
metricsParamsBuilder = metricsParamsBuilder.putParameters("utm_content", utmContent)
|
||||
}
|
||||
if (utmMedium != null) {
|
||||
metricsParamsBuilder = metricsParamsBuilder.putParameters("utm_medium", utmMedium)
|
||||
}
|
||||
if (utmTerm != null) {
|
||||
metricsParamsBuilder = metricsParamsBuilder.putParameters("utm_term", utmTerm)
|
||||
}
|
||||
if (utmCampaign != null) {
|
||||
metricsParamsBuilder = metricsParamsBuilder.putParameters("utm_campaign", utmCampaign)
|
||||
}
|
||||
if (entrypointExperiment != null) {
|
||||
metricsParamsBuilder = metricsParamsBuilder.putParameters("entrypoint_experiment", entrypointExperiment)
|
||||
}
|
||||
if (entrypointVariation != null) {
|
||||
metricsParamsBuilder = metricsParamsBuilder.putParameters("entrypoint_variation", entrypointVariation)
|
||||
}
|
||||
|
||||
return metricsParamsBuilder.build()
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package mozilla.appservices.fxaclient
|
||||
|
||||
enum class MigrationState {
|
||||
NONE,
|
||||
COPY_SESSION_TOKEN,
|
||||
REUSE_SESSION_TOKEN;
|
||||
|
||||
companion object {
|
||||
@Suppress("TooGenericExceptionThrown")
|
||||
internal fun fromNumber(v: Number): MigrationState {
|
||||
return when (v) {
|
||||
0 -> NONE
|
||||
1 -> COPY_SESSION_TOKEN
|
||||
2 -> REUSE_SESSION_TOKEN
|
||||
else -> throw RuntimeException("[Bug] unknown MigrationState value $v")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,37 +5,40 @@
|
|||
package mozilla.appservices.fxaclient
|
||||
|
||||
import android.util.Log
|
||||
import com.sun.jna.Native
|
||||
import com.sun.jna.Pointer
|
||||
import mozilla.appservices.fxaclient.rust.FxaHandle
|
||||
import mozilla.appservices.fxaclient.rust.LibFxAFFI
|
||||
import mozilla.appservices.fxaclient.rust.RustError
|
||||
import mozilla.appservices.support.native.toNioDirectBuffer
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import org.json.JSONObject
|
||||
|
||||
/**
|
||||
* FirefoxAccount represents the authentication state of a client.
|
||||
* PersistedFirefoxAccount represents the authentication state of a client.
|
||||
*
|
||||
* This is a thin wrapper around the `FirefoxAccount` object exposed from Rust.
|
||||
* Its main job is to transparently manage persistence of the account state by
|
||||
* calling the provided callback at appropriate times.
|
||||
*
|
||||
* In future it would be nice to push this logic down into the Rust code,
|
||||
* once UniFFI's support for callback interfaces is a little more battle-tested.
|
||||
*
|
||||
*/
|
||||
class FirefoxAccount(handle: FxaHandle, persistCallback: PersistCallback?) : AutoCloseable {
|
||||
private var handle: AtomicLong = AtomicLong(handle)
|
||||
class PersistedFirefoxAccount(inner: FirefoxAccount, persistCallback: PersistCallback?) : AutoCloseable {
|
||||
private var inner: FirefoxAccount = inner
|
||||
private var persistCallback: PersistCallback? = persistCallback
|
||||
|
||||
/**
|
||||
* Create a FirefoxAccount using the given config.
|
||||
* Create a PersistedFirefoxAccount using the given config.
|
||||
*
|
||||
* This does not make network requests, and can be used on the main thread.
|
||||
*
|
||||
*/
|
||||
constructor(config: Config, persistCallback: PersistCallback? = null) :
|
||||
this(rustCall { e ->
|
||||
LibFxAFFI.INSTANCE.fxa_new(
|
||||
config.contentUrl,
|
||||
config.clientId,
|
||||
config.redirectUri,
|
||||
config.tokenServerUrlOverride,
|
||||
e
|
||||
)
|
||||
}, persistCallback) {
|
||||
constructor(config: Config, persistCallback: PersistCallback? = null) : this(FirefoxAccount(
|
||||
// This is kind of dumb - we take a Config object on the Kotlin side, destructure it into its fields
|
||||
// to pass over the FFI, then the Rust side turns it back into its own variant of a Config object!
|
||||
// That made sense when we had to write the FFI layer by hand, but we should see whether we can nicely
|
||||
// expose the Rust Config interface to Kotlin and Swift and then just accept a Config here in the
|
||||
// underlying `FirefoxAccount` constructor.
|
||||
config.contentUrl,
|
||||
config.clientId,
|
||||
config.redirectUri,
|
||||
config.tokenServerUrlOverride
|
||||
), persistCallback) {
|
||||
// Persist the newly created instance state.
|
||||
this.tryPersistState()
|
||||
}
|
||||
|
@ -43,16 +46,14 @@ class FirefoxAccount(handle: FxaHandle, persistCallback: PersistCallback?) : Aut
|
|||
companion object {
|
||||
/**
|
||||
* Restores the account's authentication state from a JSON string produced by
|
||||
* [FirefoxAccount.toJSONString].
|
||||
* [PersistedFirefoxAccount.toJSONString].
|
||||
*
|
||||
* This does not make network requests, and can be used on the main thread.
|
||||
*
|
||||
* @return [FirefoxAccount] representing the authentication state
|
||||
* @return [PersistedFirefoxAccount] representing the authentication state
|
||||
*/
|
||||
fun fromJSONString(json: String, persistCallback: PersistCallback? = null): FirefoxAccount {
|
||||
return FirefoxAccount(rustCall { e ->
|
||||
LibFxAFFI.INSTANCE.fxa_from_json(json, e)
|
||||
}, persistCallback)
|
||||
fun fromJSONString(json: String, persistCallback: PersistCallback? = null): PersistedFirefoxAccount {
|
||||
return PersistedFirefoxAccount(FirefoxAccount.fromJson(json), persistCallback)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,7 +86,7 @@ class FirefoxAccount(handle: FxaHandle, persistCallback: PersistCallback?) : Aut
|
|||
val json: String
|
||||
try {
|
||||
json = this.toJSONString()
|
||||
} catch (e: FxaException) {
|
||||
} catch (e: FxaErrorException) {
|
||||
Log.e("FirefoxAccount", "Error serializing the FirefoxAccount state.")
|
||||
return
|
||||
}
|
||||
|
@ -106,21 +107,9 @@ class FirefoxAccount(handle: FxaHandle, persistCallback: PersistCallback?) : Aut
|
|||
fun beginOAuthFlow(
|
||||
scopes: Array<String>,
|
||||
entrypoint: String,
|
||||
metricsParams: MetricsParams = MetricsParams()
|
||||
metricsParams: MetricsParams = MetricsParams(mapOf())
|
||||
): String {
|
||||
val scope = scopes.joinToString(" ")
|
||||
val (nioBuf, len) = metricsParams.intoMessage().toNioDirectBuffer()
|
||||
return rustCallWithLock { e ->
|
||||
val ptr = Native.getDirectBufferPointer(nioBuf)
|
||||
LibFxAFFI.INSTANCE.fxa_begin_oauth_flow(
|
||||
this.handle.get(),
|
||||
scope,
|
||||
entrypoint,
|
||||
ptr,
|
||||
len,
|
||||
e
|
||||
)
|
||||
}.getAndConsumeRustString()
|
||||
return this.inner.beginOauthFlow(scopes.toList(), entrypoint, metricsParams)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -138,22 +127,9 @@ class FirefoxAccount(handle: FxaHandle, persistCallback: PersistCallback?) : Aut
|
|||
pairingUrl: String,
|
||||
scopes: Array<String>,
|
||||
entrypoint: String,
|
||||
metricsParams: MetricsParams = MetricsParams()
|
||||
metricsParams: MetricsParams = MetricsParams(mapOf())
|
||||
): String {
|
||||
val scope = scopes.joinToString(" ")
|
||||
val (nioBuf, len) = metricsParams.intoMessage().toNioDirectBuffer()
|
||||
return rustCallWithLock { e ->
|
||||
val ptr = Native.getDirectBufferPointer(nioBuf)
|
||||
LibFxAFFI.INSTANCE.fxa_begin_pairing_flow(
|
||||
this.handle.get(),
|
||||
pairingUrl,
|
||||
scope,
|
||||
entrypoint,
|
||||
ptr,
|
||||
len,
|
||||
e
|
||||
)
|
||||
}.getAndConsumeRustString()
|
||||
return this.inner.beginPairingFlow(pairingUrl, scopes.toList(), entrypoint, metricsParams)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -165,9 +141,7 @@ class FirefoxAccount(handle: FxaHandle, persistCallback: PersistCallback?) : Aut
|
|||
* This performs network requests, and should not be used on the main thread.
|
||||
*/
|
||||
fun completeOAuthFlow(code: String, state: String) {
|
||||
rustCallWithLock { e ->
|
||||
LibFxAFFI.INSTANCE.fxa_complete_oauth_flow(this.handle.get(), code, state, e)
|
||||
}
|
||||
this.inner.completeOauthFlow(code, state)
|
||||
this.tryPersistState()
|
||||
}
|
||||
|
||||
|
@ -183,16 +157,10 @@ class FirefoxAccount(handle: FxaHandle, persistCallback: PersistCallback?) : Aut
|
|||
* The caller should then start the OAuth Flow again with the "profile" scope.
|
||||
*/
|
||||
fun getProfile(ignoreCache: Boolean): Profile {
|
||||
val profileBuffer = rustCallWithLock { e ->
|
||||
val ignoreCacheArg: Byte = if (ignoreCache) { 1 } else { 0 }
|
||||
LibFxAFFI.INSTANCE.fxa_profile(this.handle.get(), ignoreCacheArg, e)
|
||||
}
|
||||
this.tryPersistState()
|
||||
try {
|
||||
val p = MsgTypes.Profile.parseFrom(profileBuffer.asCodedInputStream()!!)
|
||||
return Profile.fromMessage(p)
|
||||
return this.inner.getProfile(ignoreCache)
|
||||
} finally {
|
||||
LibFxAFFI.INSTANCE.fxa_bytebuffer_free(profileBuffer)
|
||||
this.tryPersistState()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -216,9 +184,7 @@ class FirefoxAccount(handle: FxaHandle, persistCallback: PersistCallback?) : Aut
|
|||
* This performs network requests, and should not be used on the main thread.
|
||||
*/
|
||||
fun getTokenServerEndpointURL(): String {
|
||||
return rustCallWithLock { e ->
|
||||
LibFxAFFI.INSTANCE.fxa_get_token_server_endpoint_url(this.handle.get(), e)
|
||||
}.getAndConsumeRustString()
|
||||
return this.inner.getTokenServerEndpointUrl()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -227,9 +193,7 @@ class FirefoxAccount(handle: FxaHandle, persistCallback: PersistCallback?) : Aut
|
|||
* This does not make network requests, and can be used on the main thread.
|
||||
*/
|
||||
fun getPairingAuthorityURL(): String {
|
||||
return rustCallWithLock { e ->
|
||||
LibFxAFFI.INSTANCE.fxa_get_pairing_authority_url(this.handle.get(), e)
|
||||
}.getAndConsumeRustString()
|
||||
return this.inner.getPairingAuthorityUrl()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -238,9 +202,7 @@ class FirefoxAccount(handle: FxaHandle, persistCallback: PersistCallback?) : Aut
|
|||
* This does not make network requests, and can be used on the main thread.
|
||||
*/
|
||||
fun getConnectionSuccessURL(): String {
|
||||
return rustCallWithLock { e ->
|
||||
LibFxAFFI.INSTANCE.fxa_get_connection_success_url(this.handle.get(), e)
|
||||
}.getAndConsumeRustString()
|
||||
return this.inner.getConnectionSuccessUrl()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -252,9 +214,7 @@ class FirefoxAccount(handle: FxaHandle, persistCallback: PersistCallback?) : Aut
|
|||
* The caller should then start the OAuth Flow again with the "profile" scope.
|
||||
*/
|
||||
fun getManageAccountURL(entrypoint: String): String {
|
||||
return rustCallWithLock { e ->
|
||||
LibFxAFFI.INSTANCE.fxa_get_manage_account_url(this.handle.get(), entrypoint, e)
|
||||
}.getAndConsumeRustString()
|
||||
return this.inner.getManageAccountUrl(entrypoint)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -266,9 +226,7 @@ class FirefoxAccount(handle: FxaHandle, persistCallback: PersistCallback?) : Aut
|
|||
* The caller should then start the OAuth Flow again with the "profile" scope.
|
||||
*/
|
||||
fun getManageDevicesURL(entrypoint: String): String {
|
||||
return rustCallWithLock { e ->
|
||||
LibFxAFFI.INSTANCE.fxa_get_manage_devices_url(this.handle.get(), entrypoint, e)
|
||||
}.getAndConsumeRustString()
|
||||
return this.inner.getManageDevicesUrl(entrypoint)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -285,29 +243,15 @@ class FirefoxAccount(handle: FxaHandle, persistCallback: PersistCallback?) : Aut
|
|||
* the desired scope.
|
||||
*/
|
||||
fun getAccessToken(scope: String, ttl: Long? = null): AccessTokenInfo {
|
||||
val buffer = rustCallWithLock { e ->
|
||||
// A zero ttl here means to use the server-controlled default.
|
||||
LibFxAFFI.INSTANCE.fxa_get_access_token(this.handle.get(), scope, ttl ?: 0L, e)
|
||||
}
|
||||
this.tryPersistState()
|
||||
try {
|
||||
val msg = MsgTypes.AccessTokenInfo.parseFrom(buffer.asCodedInputStream()!!)
|
||||
return AccessTokenInfo.fromMessage(msg)
|
||||
return this.inner.getAccessToken(scope, ttl)
|
||||
} finally {
|
||||
LibFxAFFI.INSTANCE.fxa_bytebuffer_free(buffer)
|
||||
this.tryPersistState()
|
||||
}
|
||||
}
|
||||
|
||||
fun checkAuthorizationStatus(): IntrospectInfo {
|
||||
val buffer = rustCallWithLock { e ->
|
||||
LibFxAFFI.INSTANCE.fxa_check_authorization_status(this.handle.get(), e)
|
||||
}
|
||||
try {
|
||||
val msg = MsgTypes.IntrospectInfo.parseFrom(buffer.asCodedInputStream()!!)
|
||||
return IntrospectInfo.fromMessage(msg)
|
||||
} finally {
|
||||
LibFxAFFI.INSTANCE.fxa_bytebuffer_free(buffer)
|
||||
}
|
||||
fun checkAuthorizationStatus(): AuthorizationInfo {
|
||||
return this.inner.checkAuthorizationStatus()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -316,9 +260,7 @@ class FirefoxAccount(handle: FxaHandle, persistCallback: PersistCallback?) : Aut
|
|||
* @throws FxaException Will send you an exception if there is no session token set
|
||||
*/
|
||||
fun getSessionToken(): String {
|
||||
return rustCallWithLock { e ->
|
||||
LibFxAFFI.INSTANCE.fxa_get_session_token(this.handle.get(), e)
|
||||
}.getAndConsumeRustString()
|
||||
return this.inner.getSessionToken()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -327,9 +269,7 @@ class FirefoxAccount(handle: FxaHandle, persistCallback: PersistCallback?) : Aut
|
|||
* @throws FxaException Will send you an exception if there is no device id set
|
||||
*/
|
||||
fun getCurrentDeviceId(): String {
|
||||
return rustCallWithLock { e ->
|
||||
LibFxAFFI.INSTANCE.fxa_get_current_device_id(this.handle.get(), e)
|
||||
}.getAndConsumeRustString()
|
||||
return this.inner.getCurrentDeviceId()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -339,18 +279,9 @@ class FirefoxAccount(handle: FxaHandle, persistCallback: PersistCallback?) : Aut
|
|||
* This performs network requests, and should not be used on the main thread.
|
||||
*/
|
||||
fun authorizeOAuthCode(
|
||||
authParams: AuthorizationParams
|
||||
authParams: AuthorizationParameters
|
||||
): String {
|
||||
val (nioBuf, len) = authParams.intoMessage().toNioDirectBuffer()
|
||||
return rustCallWithLock { e ->
|
||||
val ptr = Native.getDirectBufferPointer(nioBuf)
|
||||
LibFxAFFI.INSTANCE.fxa_authorize_auth_code(
|
||||
this.handle.get(),
|
||||
ptr,
|
||||
len,
|
||||
e
|
||||
)
|
||||
}.getAndConsumeRustString()
|
||||
return this.inner.authorizeCodeUsingSessionToken(authParams)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -361,9 +292,7 @@ class FirefoxAccount(handle: FxaHandle, persistCallback: PersistCallback?) : Aut
|
|||
* again.
|
||||
*/
|
||||
fun clearAccessTokenCache() {
|
||||
rustCallWithLock { e ->
|
||||
LibFxAFFI.INSTANCE.fxa_clear_access_token_cache(this.handle.get(), e)
|
||||
}
|
||||
this.inner.clearAccessTokenCache()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -378,17 +307,8 @@ class FirefoxAccount(handle: FxaHandle, persistCallback: PersistCallback?) : Aut
|
|||
*/
|
||||
fun migrateFromSessionToken(sessionToken: String, kSync: String, kXCS: String): JSONObject {
|
||||
try {
|
||||
val json = rustCallWithLock { e ->
|
||||
LibFxAFFI.INSTANCE.fxa_migrate_from_session_token(
|
||||
this.handle.get(),
|
||||
sessionToken,
|
||||
kSync,
|
||||
kXCS,
|
||||
0,
|
||||
e
|
||||
)
|
||||
}.getAndConsumeRustString()
|
||||
return JSONObject(json)
|
||||
val res = this.inner.migrateFromSessionToken(sessionToken, kSync, kXCS, false)
|
||||
return JSONObject(mapOf("total_duration" to res.totalDuration))
|
||||
} finally {
|
||||
// Even a failed migration might alter the persisted account state, if it's able to be retried.
|
||||
// It's safe to call this unconditionally, as the underlying code will not leave partial states.
|
||||
|
@ -402,12 +322,7 @@ class FirefoxAccount(handle: FxaHandle, persistCallback: PersistCallback?) : Aut
|
|||
* @return bool Returns a boolean if we are in a migration state
|
||||
*/
|
||||
fun isInMigrationState(): MigrationState {
|
||||
// Bug: see https://youtrack.jetbrains.com/issue/KT-32104.
|
||||
@Suppress("IMPLICIT_NOTHING_AS_TYPE_PARAMETER")
|
||||
rustCall { e ->
|
||||
val state = LibFxAFFI.INSTANCE.fxa_is_in_migration_state(this.handle.get(), e)
|
||||
return MigrationState.fromNumber(state.toInt())
|
||||
}
|
||||
return this.inner.isInMigrationState()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -422,10 +337,8 @@ class FirefoxAccount(handle: FxaHandle, persistCallback: PersistCallback?) : Aut
|
|||
*/
|
||||
fun copyFromSessionToken(sessionToken: String, kSync: String, kXCS: String): JSONObject {
|
||||
try {
|
||||
val json = rustCallWithLock { e ->
|
||||
LibFxAFFI.INSTANCE.fxa_migrate_from_session_token(this.handle.get(), sessionToken, kSync, kXCS, 1, e)
|
||||
}.getAndConsumeRustString()
|
||||
return JSONObject(json)
|
||||
val res = this.inner.migrateFromSessionToken(sessionToken, kSync, kXCS, true)
|
||||
return JSONObject(mapOf("total_duration" to res.totalDuration))
|
||||
} finally {
|
||||
// Even a failed migration might alter the persisted account state, if it's able to be retried.
|
||||
// It's safe to call this unconditionally, as the underlying code will not leave partial states.
|
||||
|
@ -442,10 +355,8 @@ class FirefoxAccount(handle: FxaHandle, persistCallback: PersistCallback?) : Aut
|
|||
*/
|
||||
fun retryMigrateFromSessionToken(): JSONObject {
|
||||
try {
|
||||
val json = rustCallWithLock { e ->
|
||||
LibFxAFFI.INSTANCE.fxa_retry_migrate_from_session_token(this.handle.get(), e)
|
||||
}.getAndConsumeRustString()
|
||||
return JSONObject(json)
|
||||
val res = this.inner.retryMigrateFromSessionToken()
|
||||
return JSONObject(mapOf("total_duration" to res.totalDuration))
|
||||
} finally {
|
||||
// A failure her might alter the persisted account state, if we discover a permanent migration failure.
|
||||
// It's safe to call this unconditionally, as the underlying code will not leave partial states.
|
||||
|
@ -463,9 +374,7 @@ class FirefoxAccount(handle: FxaHandle, persistCallback: PersistCallback?) : Aut
|
|||
* @return String containing the authentication details in JSON format
|
||||
*/
|
||||
fun toJSONString(): String {
|
||||
return rustCallWithLock { e ->
|
||||
LibFxAFFI.INSTANCE.fxa_to_json(this.handle.get(), e)
|
||||
}.getAndConsumeRustString()
|
||||
return this.inner.toJson()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -479,8 +388,10 @@ class FirefoxAccount(handle: FxaHandle, persistCallback: PersistCallback?) : Aut
|
|||
* @param authKey Auth key used to encrypt push payloads
|
||||
*/
|
||||
fun setDevicePushSubscription(endpoint: String, publicKey: String, authKey: String) {
|
||||
rustCall { e ->
|
||||
LibFxAFFI.INSTANCE.fxa_set_push_subscription(this.handle.get(), endpoint, publicKey, authKey, e)
|
||||
try {
|
||||
return this.inner.setPushSubscription(DevicePushSubscription(endpoint, publicKey, authKey))
|
||||
} finally {
|
||||
this.tryPersistState()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -493,8 +404,10 @@ class FirefoxAccount(handle: FxaHandle, persistCallback: PersistCallback?) : Aut
|
|||
* @param displayName The current device display name
|
||||
*/
|
||||
fun setDeviceDisplayName(displayName: String) {
|
||||
rustCall { e ->
|
||||
LibFxAFFI.INSTANCE.fxa_set_device_name(this.handle.get(), displayName, e)
|
||||
try {
|
||||
return this.inner.setDeviceName(displayName)
|
||||
} finally {
|
||||
this.tryPersistState()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -504,16 +417,7 @@ class FirefoxAccount(handle: FxaHandle, persistCallback: PersistCallback?) : Aut
|
|||
* This performs network requests, and should not be used on the main thread.
|
||||
*/
|
||||
fun getDevices(ignoreCache: Boolean = false): Array<Device> {
|
||||
val devicesBuffer = rustCall { e ->
|
||||
val ignoreCacheArg: Byte = if (ignoreCache) { 1 } else { 0 }
|
||||
LibFxAFFI.INSTANCE.fxa_get_devices(this.handle.get(), ignoreCacheArg, e)
|
||||
}
|
||||
try {
|
||||
val devices = MsgTypes.Devices.parseFrom(devicesBuffer.asCodedInputStream()!!)
|
||||
return Device.fromCollectionMessage(devices)
|
||||
} finally {
|
||||
LibFxAFFI.INSTANCE.fxa_bytebuffer_free(devicesBuffer)
|
||||
}
|
||||
return this.inner.getDevices(ignoreCache).toTypedArray()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -523,9 +427,7 @@ class FirefoxAccount(handle: FxaHandle, persistCallback: PersistCallback?) : Aut
|
|||
* This performs network requests, and should not be used on the main thread.
|
||||
*/
|
||||
fun disconnect() {
|
||||
rustCall { e ->
|
||||
LibFxAFFI.INSTANCE.fxa_disconnect(this.handle.get(), e)
|
||||
}
|
||||
this.inner.disconnect()
|
||||
this.tryPersistState()
|
||||
}
|
||||
|
||||
|
@ -541,15 +443,10 @@ class FirefoxAccount(handle: FxaHandle, persistCallback: PersistCallback?) : Aut
|
|||
* @return A collection of [IncomingDeviceCommand] that should be handled by the caller.
|
||||
*/
|
||||
fun pollDeviceCommands(): Array<IncomingDeviceCommand> {
|
||||
val eventsBuffer = rustCall { e ->
|
||||
LibFxAFFI.INSTANCE.fxa_poll_device_commands(this.handle.get(), e)
|
||||
}
|
||||
this.tryPersistState()
|
||||
try {
|
||||
val commands = MsgTypes.IncomingDeviceCommands.parseFrom(eventsBuffer.asCodedInputStream()!!)
|
||||
return IncomingDeviceCommand.fromCollectionMessage(commands)
|
||||
return this.inner.pollDeviceCommands().toTypedArray()
|
||||
} finally {
|
||||
LibFxAFFI.INSTANCE.fxa_bytebuffer_free(eventsBuffer)
|
||||
this.tryPersistState()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -562,15 +459,10 @@ class FirefoxAccount(handle: FxaHandle, persistCallback: PersistCallback?) : Aut
|
|||
* @return A collection of [AccountEvent] that should be handled by the caller.
|
||||
*/
|
||||
fun handlePushMessage(payload: String): Array<AccountEvent> {
|
||||
val eventsBuffer = rustCall { e ->
|
||||
LibFxAFFI.INSTANCE.fxa_handle_push_message(this.handle.get(), payload, e)
|
||||
}
|
||||
this.tryPersistState()
|
||||
try {
|
||||
val events = MsgTypes.AccountEvents.parseFrom(eventsBuffer.asCodedInputStream()!!)
|
||||
return AccountEvent.fromCollectionMessage(events)
|
||||
return this.inner.handlePushMessage(payload).toTypedArray()
|
||||
} finally {
|
||||
LibFxAFFI.INSTANCE.fxa_bytebuffer_free(eventsBuffer)
|
||||
this.tryPersistState()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -581,12 +473,8 @@ class FirefoxAccount(handle: FxaHandle, persistCallback: PersistCallback?) : Aut
|
|||
*
|
||||
* This performs network requests, and should not be used on the main thread.
|
||||
*/
|
||||
fun initializeDevice(name: String, deviceType: Device.Type, supportedCapabilities: Set<Device.Capability>) {
|
||||
val (nioBuf, len) = supportedCapabilities.toCollectionMessage().toNioDirectBuffer()
|
||||
rustCall { e ->
|
||||
val ptr = Native.getDirectBufferPointer(nioBuf)
|
||||
LibFxAFFI.INSTANCE.fxa_initialize_device(this.handle.get(), name, deviceType.toNumber(), ptr, len, e)
|
||||
}
|
||||
fun initializeDevice(name: String, deviceType: DeviceType, supportedCapabilities: Set<DeviceCapability>) {
|
||||
this.inner.initializeDevice(name, deviceType, supportedCapabilities.toList())
|
||||
this.tryPersistState()
|
||||
}
|
||||
|
||||
|
@ -600,12 +488,8 @@ class FirefoxAccount(handle: FxaHandle, persistCallback: PersistCallback?) : Aut
|
|||
*
|
||||
* This performs network requests, and should not be used on the main thread.
|
||||
*/
|
||||
fun ensureCapabilities(supportedCapabilities: Set<Device.Capability>) {
|
||||
val (nioBuf, len) = supportedCapabilities.toCollectionMessage().toNioDirectBuffer()
|
||||
rustCall { e ->
|
||||
val ptr = Native.getDirectBufferPointer(nioBuf)
|
||||
LibFxAFFI.INSTANCE.fxa_ensure_capabilities(this.handle.get(), ptr, len, e)
|
||||
}
|
||||
fun ensureCapabilities(supportedCapabilities: Set<DeviceCapability>) {
|
||||
this.inner.ensureCapabilities(supportedCapabilities.toList())
|
||||
this.tryPersistState()
|
||||
}
|
||||
|
||||
|
@ -619,9 +503,7 @@ class FirefoxAccount(handle: FxaHandle, persistCallback: PersistCallback?) : Aut
|
|||
* @param url The url of the tab being sent
|
||||
*/
|
||||
fun sendSingleTab(targetDeviceId: String, title: String, url: String) {
|
||||
rustCall { e ->
|
||||
LibFxAFFI.INSTANCE.fxa_send_tab(this.handle.get(), targetDeviceId, title, url, e)
|
||||
}
|
||||
this.inner.sendSingleTab(targetDeviceId, title, url)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -631,71 +513,11 @@ class FirefoxAccount(handle: FxaHandle, persistCallback: PersistCallback?) : Aut
|
|||
* This does not make network requests, and can be used on the main thread.
|
||||
*/
|
||||
fun gatherTelemetry(): String {
|
||||
return rustCallWithLock { e ->
|
||||
LibFxAFFI.INSTANCE.fxa_gather_telemetry(this.handle.get(), e)
|
||||
}.getAndConsumeRustString()
|
||||
return this.inner.gatherTelemetry()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun close() {
|
||||
val handle = this.handle.getAndSet(0)
|
||||
if (handle != 0L) {
|
||||
rustCall { err ->
|
||||
LibFxAFFI.INSTANCE.fxa_free(handle, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <U> nullableRustCallWithLock(callback: (RustError.ByReference) -> U?): U? {
|
||||
return synchronized(this) {
|
||||
nullableRustCall { callback(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <U> rustCallWithLock(callback: (RustError.ByReference) -> U?): U {
|
||||
return nullableRustCallWithLock(callback)!!
|
||||
this.inner.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
// In practice we usually need to be synchronized to call this safely, so it doesn't
|
||||
// synchronize itself
|
||||
private inline fun <U> nullableRustCall(callback: (RustError.ByReference) -> U?): U? {
|
||||
val e = RustError.ByReference()
|
||||
try {
|
||||
val ret = callback(e)
|
||||
if (e.isFailure()) {
|
||||
throw e.intoException()
|
||||
}
|
||||
return ret
|
||||
} finally {
|
||||
// This only matters if `callback` throws (or does a non-local return, which
|
||||
// we currently don't do)
|
||||
e.ensureConsumed()
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <U> rustCall(callback: (RustError.ByReference) -> U?): U {
|
||||
return nullableRustCall(callback)!!
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to read a null terminated String out of the Pointer and free it.
|
||||
*
|
||||
* Important: Do not use this pointer after this! For anything!
|
||||
*/
|
||||
internal fun Pointer.getAndConsumeRustString(): String {
|
||||
try {
|
||||
return this.getRustString()
|
||||
} finally {
|
||||
LibFxAFFI.INSTANCE.fxa_str_free(this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to read a null terminated string out of the pointer.
|
||||
*
|
||||
* Important: doesn't free the pointer, use [getAndConsumeRustString] for that!
|
||||
*/
|
||||
internal fun Pointer.getRustString(): String {
|
||||
return this.getString(0, "utf8")
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package mozilla.appservices.fxaclient
|
||||
|
||||
data class Profile(
|
||||
val uid: String?,
|
||||
val email: String?,
|
||||
val avatar: String?,
|
||||
val avatarDefault: Boolean,
|
||||
val displayName: String?
|
||||
) {
|
||||
companion object {
|
||||
internal fun fromMessage(msg: MsgTypes.Profile): Profile {
|
||||
return Profile(uid = if (msg.hasUid()) msg.uid else null,
|
||||
email = if (msg.hasEmail()) msg.email else null,
|
||||
avatar = if (msg.hasAvatar()) msg.avatar else null,
|
||||
avatarDefault = msg.avatarDefault,
|
||||
displayName = if (msg.hasDisplayName()) msg.displayName else null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,118 +0,0 @@
|
|||
@file:Suppress("MaxLineLength")
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package mozilla.appservices.fxaclient.rust
|
||||
|
||||
import com.sun.jna.Library
|
||||
import com.sun.jna.Pointer
|
||||
import mozilla.appservices.support.native.RustBuffer
|
||||
import mozilla.appservices.support.native.loadIndirect
|
||||
import org.mozilla.appservices.fxaclient.BuildConfig
|
||||
|
||||
@Suppress("FunctionNaming", "FunctionParameterNaming", "LongParameterList", "TooGenericExceptionThrown")
|
||||
internal interface LibFxAFFI : Library {
|
||||
companion object {
|
||||
internal var INSTANCE: LibFxAFFI =
|
||||
loadIndirect(componentName = "fxaclient", componentVersion = BuildConfig.LIBRARY_VERSION)
|
||||
}
|
||||
|
||||
fun fxa_new(
|
||||
contentUrl: String,
|
||||
clientId: String,
|
||||
redirectUri: String,
|
||||
tokenServerUrlOverride: String?,
|
||||
e: RustError.ByReference
|
||||
): FxaHandle
|
||||
|
||||
fun fxa_from_json(json: String, e: RustError.ByReference): FxaHandle
|
||||
fun fxa_to_json(fxa: Long, e: RustError.ByReference): Pointer?
|
||||
|
||||
fun fxa_begin_oauth_flow(
|
||||
fxa: FxaHandle,
|
||||
scopes: String,
|
||||
entrypoint: String,
|
||||
metrics_params: Pointer,
|
||||
metrics_params_len: Int,
|
||||
e: RustError.ByReference
|
||||
): Pointer?
|
||||
|
||||
fun fxa_begin_pairing_flow(
|
||||
fxa: FxaHandle,
|
||||
pairingUrl: String,
|
||||
scopes: String,
|
||||
entrypoint: String,
|
||||
metrics_params: Pointer,
|
||||
metrics_params_len: Int,
|
||||
e: RustError.ByReference
|
||||
): Pointer?
|
||||
|
||||
fun fxa_profile(fxa: FxaHandle, ignoreCache: Byte, e: RustError.ByReference): RustBuffer.ByValue
|
||||
|
||||
fun fxa_get_token_server_endpoint_url(fxa: FxaHandle, e: RustError.ByReference): Pointer?
|
||||
fun fxa_get_pairing_authority_url(fxa: FxaHandle, e: RustError.ByReference): Pointer?
|
||||
fun fxa_get_connection_success_url(fxa: FxaHandle, e: RustError.ByReference): Pointer?
|
||||
fun fxa_get_manage_account_url(fxa: FxaHandle, entrypoint: String, e: RustError.ByReference): Pointer?
|
||||
fun fxa_get_manage_devices_url(fxa: FxaHandle, entrypoint: String, e: RustError.ByReference): Pointer?
|
||||
|
||||
fun fxa_complete_oauth_flow(fxa: FxaHandle, code: String, state: String, e: RustError.ByReference)
|
||||
fun fxa_get_access_token(fxa: FxaHandle, scope: String, ttl: Long, e: RustError.ByReference): RustBuffer.ByValue
|
||||
fun fxa_get_session_token(fxa: FxaHandle, e: RustError.ByReference): Pointer?
|
||||
fun fxa_get_current_device_id(fxa: FxaHandle, e: RustError.ByReference): Pointer?
|
||||
fun fxa_authorize_auth_code(fxa: FxaHandle, auth_params: Pointer, auth_params_len: Int, e: RustError.ByReference): Pointer?
|
||||
fun fxa_check_authorization_status(fxa: FxaHandle, e: RustError.ByReference): RustBuffer.ByValue
|
||||
fun fxa_clear_access_token_cache(fxa: FxaHandle, e: RustError.ByReference)
|
||||
|
||||
fun fxa_set_push_subscription(
|
||||
fxa: FxaHandle,
|
||||
endpoint: String,
|
||||
publicKey: String,
|
||||
authKey: String,
|
||||
e: RustError.ByReference
|
||||
)
|
||||
fun fxa_set_device_name(fxa: FxaHandle, displayName: String, e: RustError.ByReference)
|
||||
fun fxa_get_devices(fxa: FxaHandle, ignoreCache: Byte, e: RustError.ByReference): RustBuffer.ByValue
|
||||
fun fxa_disconnect(fxa: FxaHandle, e: RustError.ByReference)
|
||||
fun fxa_poll_device_commands(fxa: FxaHandle, e: RustError.ByReference): RustBuffer.ByValue
|
||||
fun fxa_handle_push_message(fxa: FxaHandle, jsonPayload: String, e: RustError.ByReference): RustBuffer.ByValue
|
||||
|
||||
fun fxa_initialize_device(
|
||||
fxa: FxaHandle,
|
||||
name: String,
|
||||
type: Int,
|
||||
capabilities_data: Pointer,
|
||||
capabilities_len: Int,
|
||||
e: RustError.ByReference
|
||||
)
|
||||
fun fxa_ensure_capabilities(
|
||||
fxa: FxaHandle,
|
||||
capabilities_data: Pointer,
|
||||
capabilities_len: Int,
|
||||
e: RustError.ByReference
|
||||
)
|
||||
fun fxa_send_tab(fxa: FxaHandle, targetDeviceId: String, title: String, url: String, e: RustError.ByReference)
|
||||
|
||||
fun fxa_migrate_from_session_token(
|
||||
fxa: FxaHandle,
|
||||
sessionToken: String,
|
||||
kSync: String,
|
||||
kXCS: String,
|
||||
copySessionToken: Byte,
|
||||
e: RustError.ByReference
|
||||
): Pointer?
|
||||
|
||||
fun fxa_is_in_migration_state(
|
||||
fxa: FxaHandle,
|
||||
e: RustError.ByReference
|
||||
): Byte
|
||||
|
||||
fun fxa_retry_migrate_from_session_token(fxa: FxaHandle, e: RustError.ByReference): Pointer?
|
||||
|
||||
fun fxa_gather_telemetry(fxa: FxaHandle, e: RustError.ByReference): Pointer?
|
||||
|
||||
fun fxa_str_free(string: Pointer)
|
||||
fun fxa_bytebuffer_free(buffer: RustBuffer.ByValue)
|
||||
fun fxa_free(fxa: FxaHandle, err: RustError.ByReference)
|
||||
}
|
||||
internal typealias FxaHandle = Long
|
|
@ -1,80 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package mozilla.appservices.fxaclient.rust
|
||||
|
||||
import com.sun.jna.Pointer
|
||||
import com.sun.jna.Structure
|
||||
import mozilla.appservices.fxaclient.FxaException
|
||||
import mozilla.appservices.fxaclient.getAndConsumeRustString
|
||||
|
||||
@Structure.FieldOrder("code", "message")
|
||||
internal open class RustError : Structure() {
|
||||
|
||||
class ByReference : RustError(), Structure.ByReference
|
||||
|
||||
@JvmField var code: Int = 0
|
||||
@JvmField var message: Pointer? = null
|
||||
|
||||
/**
|
||||
* Does this represent success?
|
||||
*/
|
||||
fun isSuccess(): Boolean {
|
||||
return code == 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this represent failure?
|
||||
*/
|
||||
fun isFailure(): Boolean {
|
||||
return code != 0
|
||||
}
|
||||
|
||||
@Suppress("ReturnCount", "TooGenericExceptionThrown")
|
||||
fun intoException(): FxaException {
|
||||
if (!isFailure()) {
|
||||
// It's probably a bad idea to throw here! We're probably leaking something if this is
|
||||
// ever hit! (But we shouldn't ever hit it?)
|
||||
throw RuntimeException("[Bug] intoException called on non-failure!")
|
||||
}
|
||||
val message = this.consumeErrorMessage()
|
||||
when (code) {
|
||||
3 -> return FxaException.Network(message)
|
||||
2 -> return FxaException.Unauthorized(message)
|
||||
-1 -> return FxaException.Panic(message)
|
||||
// Note: `1` is used as a generic catch all, but we
|
||||
// might as well handle the others the same way.
|
||||
else -> return FxaException.Unspecified(message)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get and consume the error message, or null if there is none.
|
||||
*/
|
||||
@Synchronized
|
||||
fun consumeErrorMessage(): String {
|
||||
val result = this.getMessage()
|
||||
if (this.message != null) {
|
||||
LibFxAFFI.INSTANCE.fxa_str_free(this.message!!)
|
||||
this.message = null
|
||||
}
|
||||
if (result == null) {
|
||||
throw NullPointerException("consumeErrorMessage called with null message!")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun ensureConsumed() {
|
||||
this.message?.getAndConsumeRustString()
|
||||
this.message = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error message or null if there is none.
|
||||
*/
|
||||
fun getMessage(): String? {
|
||||
return this.message?.getString(0, "utf8")
|
||||
}
|
||||
}
|
|
@ -2,4 +2,6 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
pub mod send_tab;
|
||||
fn main() {
|
||||
uniffi_build::generate_scaffolding("./src/fxa_client.udl").unwrap();
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
[package]
|
||||
name = "fxaclient_ffi"
|
||||
edition = "2018"
|
||||
version = "0.1.0"
|
||||
authors = ["Edouard Oger <eoger@fastmail.com>"]
|
||||
license = "MPL-2.0"
|
||||
|
||||
[lib]
|
||||
name = "fxaclient_ffi"
|
||||
crate-type = ["lib"]
|
||||
|
||||
[dependencies]
|
||||
ffi-support = "0.4"
|
||||
log = "0.4"
|
||||
serde_json = "1"
|
||||
lazy_static = "1.4"
|
||||
url = "2.1"
|
||||
prost = "0.6"
|
||||
viaduct = { path = "../../viaduct" }
|
||||
|
||||
[dependencies.fxa-client]
|
||||
path = "../"
|
|
@ -1,624 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#![allow(unknown_lints)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
// Let's allow these in the FFI code, since it's usually just a coincidence if
|
||||
// the closure is small.
|
||||
#![allow(clippy::redundant_closure)]
|
||||
use ffi_support::{
|
||||
define_bytebuffer_destructor, define_handle_map_deleter, define_string_destructor, ByteBuffer,
|
||||
ConcurrentHandleMap, ExternError, FfiStr,
|
||||
};
|
||||
use fxa_client::{
|
||||
device::{Capability as DeviceCapability, CommandFetchReason, PushSubscription},
|
||||
ffi::{from_protobuf_ptr, AuthorizationParameters, MetricsParams},
|
||||
migrator::MigrationState,
|
||||
msg_types, FirefoxAccount,
|
||||
};
|
||||
use std::os::raw::c_char;
|
||||
use url::Url;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref ACCOUNTS: ConcurrentHandleMap<FirefoxAccount> = ConcurrentHandleMap::new();
|
||||
}
|
||||
|
||||
/// Creates a [FirefoxAccount].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// A destructor [fxa_free] is provided for releasing the memory for this
|
||||
/// pointer type.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fxa_new(
|
||||
content_url: FfiStr<'_>,
|
||||
client_id: FfiStr<'_>,
|
||||
redirect_uri: FfiStr<'_>,
|
||||
token_server_url_override: FfiStr<'_>,
|
||||
err: &mut ExternError,
|
||||
) -> u64 {
|
||||
log::debug!("fxa_new");
|
||||
ACCOUNTS.insert_with_output(err, || {
|
||||
let content_url = content_url.as_str();
|
||||
let client_id = client_id.as_str();
|
||||
let redirect_uri = redirect_uri.as_str();
|
||||
let token_server_url_override = token_server_url_override.as_opt_str();
|
||||
FirefoxAccount::new(
|
||||
content_url,
|
||||
client_id,
|
||||
redirect_uri,
|
||||
token_server_url_override,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Restore a [FirefoxAccount] instance from an serialized state (created with [fxa_to_json]).
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// A destructor [fxa_free] is provided for releasing the memory for this
|
||||
/// pointer type.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fxa_from_json(json: FfiStr<'_>, err: &mut ExternError) -> u64 {
|
||||
log::debug!("fxa_from_json");
|
||||
ACCOUNTS.insert_with_result(err, || FirefoxAccount::from_json(json.as_str()))
|
||||
}
|
||||
|
||||
/// Serializes the state of a [FirefoxAccount] instance. It can be restored later with [fxa_from_json].
|
||||
///
|
||||
/// It is the responsability of the caller to persist that serialized state regularly (after operations that mutate [FirefoxAccount])
|
||||
/// in a **secure** location.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// A destructor [fxa_str_free] is provided for releasing the memory for this
|
||||
/// pointer type.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fxa_to_json(handle: u64, error: &mut ExternError) -> *mut c_char {
|
||||
log::debug!("fxa_to_json");
|
||||
ACCOUNTS.call_with_result_mut(error, handle, |fxa| fxa.to_json())
|
||||
}
|
||||
|
||||
/// Fetches the profile associated with a Firefox Account.
|
||||
///
|
||||
/// The profile might get cached in-memory and the caller might get served a cached version.
|
||||
/// To bypass this, the `ignore_cache` parameter can be set to `true`.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// A destructor [fxa_bytebuffer_free] is provided for releasing the memory for this
|
||||
/// pointer type.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fxa_profile(
|
||||
handle: u64,
|
||||
ignore_cache: u8,
|
||||
error: &mut ExternError,
|
||||
) -> ByteBuffer {
|
||||
log::debug!("fxa_profile");
|
||||
ACCOUNTS.call_with_result_mut(error, handle, |fxa| fxa.get_profile(ignore_cache != 0))
|
||||
}
|
||||
|
||||
/// Get the pairing URL to navigate to on the Auth side (typically a computer).
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// A destructor [fxa_str_free] is provided for releasing the memory for this
|
||||
/// pointer type.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fxa_get_pairing_authority_url(
|
||||
handle: u64,
|
||||
error: &mut ExternError,
|
||||
) -> *mut c_char {
|
||||
log::debug!("fxa_get_pairing_authority_url");
|
||||
ACCOUNTS.call_with_result(error, handle, |fxa| {
|
||||
fxa.get_pairing_authority_url().map(Url::into_string)
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the Sync token server endpoint URL.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// A destructor [fxa_str_free] is provided for releasing the memory for this
|
||||
/// pointer type.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fxa_get_token_server_endpoint_url(
|
||||
handle: u64,
|
||||
error: &mut ExternError,
|
||||
) -> *mut c_char {
|
||||
log::debug!("fxa_get_token_server_endpoint_url");
|
||||
ACCOUNTS.call_with_result(error, handle, |fxa| {
|
||||
fxa.get_token_server_endpoint_url().map(Url::into_string)
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the url to redirect after there has been a successful connection to FxA.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// A destructor [fxa_str_free] is provided for releasing the memory for this
|
||||
/// pointer type.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fxa_get_connection_success_url(
|
||||
handle: u64,
|
||||
error: &mut ExternError,
|
||||
) -> *mut c_char {
|
||||
log::debug!("fxa_get_connection_success_url");
|
||||
ACCOUNTS.call_with_result(error, handle, |fxa| {
|
||||
fxa.get_connection_success_url().map(Url::into_string)
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the url to open the user's account-management page.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// A destructor [fxa_str_free] is provided for releasing the memory for this
|
||||
/// pointer type.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fxa_get_manage_account_url(
|
||||
handle: u64,
|
||||
entrypoint: FfiStr<'_>,
|
||||
error: &mut ExternError,
|
||||
) -> *mut c_char {
|
||||
log::debug!("fxa_get_manage_account_url");
|
||||
ACCOUNTS.call_with_result_mut(error, handle, |fxa| {
|
||||
fxa.get_manage_account_url(entrypoint.as_str())
|
||||
.map(Url::into_string)
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the url to open the user's devices-management page.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// A destructor [fxa_str_free] is provided for releasing the memory for this
|
||||
/// pointer type.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fxa_get_manage_devices_url(
|
||||
handle: u64,
|
||||
entrypoint: FfiStr<'_>,
|
||||
error: &mut ExternError,
|
||||
) -> *mut c_char {
|
||||
log::debug!("fxa_get_manage_devices_url");
|
||||
ACCOUNTS.call_with_result_mut(error, handle, |fxa| {
|
||||
fxa.get_manage_devices_url(entrypoint.as_str())
|
||||
.map(Url::into_string)
|
||||
})
|
||||
}
|
||||
|
||||
/// Request a OAuth token by starting a new pairing flow, by calling the content server pairing endpoint.
|
||||
///
|
||||
/// This function returns a URL string that the caller should open in a webview.
|
||||
///
|
||||
/// Pairing assumes you want keys by default, so you must provide a key-bearing scope.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// A destructor [fxa_str_free] is provided for releasing the memory for this
|
||||
/// pointer type.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn fxa_begin_pairing_flow(
|
||||
handle: u64,
|
||||
pairing_url: FfiStr<'_>,
|
||||
scope: FfiStr<'_>,
|
||||
entrypoint: FfiStr<'_>,
|
||||
metrics_params: *const u8,
|
||||
metrics_params_len: i32,
|
||||
error: &mut ExternError,
|
||||
) -> *mut c_char {
|
||||
log::debug!("fxa_begin_pairing_flow");
|
||||
ACCOUNTS.call_with_result_mut(error, handle, |fxa| {
|
||||
let pairing_url = pairing_url.as_str();
|
||||
let scope = scope.as_str();
|
||||
let scopes: Vec<&str> = scope.split(' ').collect();
|
||||
let metrics_params = from_protobuf_ptr::<MetricsParams, msg_types::MetricsParams>(
|
||||
metrics_params,
|
||||
metrics_params_len,
|
||||
)?;
|
||||
fxa.begin_pairing_flow(
|
||||
&pairing_url,
|
||||
&scopes,
|
||||
entrypoint.as_str(),
|
||||
Some(metrics_params),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Request a OAuth token by starting a new OAuth flow.
|
||||
///
|
||||
/// This function returns a URL string that the caller should open in a webview.
|
||||
///
|
||||
/// Once the user has confirmed the authorization grant, they will get redirected to `redirect_url`:
|
||||
/// the caller must intercept that redirection, extract the `code` and `state` query parameters and call
|
||||
/// [fxa_complete_oauth_flow] to complete the flow.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// A destructor [fxa_str_free] is provided for releasing the memory for this
|
||||
/// pointer type.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn fxa_begin_oauth_flow(
|
||||
handle: u64,
|
||||
scope: FfiStr<'_>,
|
||||
entrypoint: FfiStr<'_>,
|
||||
metrics_params: *const u8,
|
||||
metrics_params_len: i32,
|
||||
error: &mut ExternError,
|
||||
) -> *mut c_char {
|
||||
log::debug!("fxa_begin_oauth_flow");
|
||||
ACCOUNTS.call_with_result_mut(error, handle, |fxa| {
|
||||
let scope = scope.as_str();
|
||||
let scopes: Vec<&str> = scope.split(' ').collect();
|
||||
let metrics_params = from_protobuf_ptr::<MetricsParams, msg_types::MetricsParams>(
|
||||
metrics_params,
|
||||
metrics_params_len,
|
||||
)?;
|
||||
fxa.begin_oauth_flow(&scopes, entrypoint.as_str(), Some(metrics_params))
|
||||
})
|
||||
}
|
||||
|
||||
/// Finish an OAuth flow initiated by [fxa_begin_oauth_flow].
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fxa_complete_oauth_flow(
|
||||
handle: u64,
|
||||
code: FfiStr<'_>,
|
||||
state: FfiStr<'_>,
|
||||
error: &mut ExternError,
|
||||
) {
|
||||
log::debug!("fxa_complete_oauth_flow");
|
||||
ACCOUNTS.call_with_result_mut(error, handle, |fxa| {
|
||||
let code = code.as_str();
|
||||
let state = state.as_str();
|
||||
fxa.complete_oauth_flow(code, state)
|
||||
});
|
||||
}
|
||||
|
||||
/// Migrate from a logged-in sessionToken Firefox Account.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fxa_migrate_from_session_token(
|
||||
handle: u64,
|
||||
session_token: FfiStr<'_>,
|
||||
k_sync: FfiStr<'_>,
|
||||
k_xcs: FfiStr<'_>,
|
||||
copy_session_token: u8,
|
||||
error: &mut ExternError,
|
||||
) -> *mut c_char {
|
||||
log::debug!("fxa_migrate_from_session_token");
|
||||
ACCOUNTS.call_with_result_mut(error, handle, |fxa| -> fxa_client::Result<String> {
|
||||
let session_token = session_token.as_str();
|
||||
let k_sync = k_sync.as_str();
|
||||
let k_xcs = k_xcs.as_str();
|
||||
let migration_metrics =
|
||||
fxa.migrate_from_session_token(session_token, k_sync, k_xcs, copy_session_token != 0)?;
|
||||
let result = serde_json::to_string(&migration_metrics)?;
|
||||
Ok(result)
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if there is migration state.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fxa_is_in_migration_state(handle: u64, error: &mut ExternError) -> u8 {
|
||||
log::debug!("fxa_is_in_migration_state");
|
||||
ACCOUNTS.call_with_result(error, handle, |fxa| -> fxa_client::Result<MigrationState> {
|
||||
Ok(fxa.is_in_migration_state())
|
||||
})
|
||||
}
|
||||
|
||||
/// Retry the migration attempt using the stored migration state.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fxa_retry_migrate_from_session_token(
|
||||
handle: u64,
|
||||
error: &mut ExternError,
|
||||
) -> *mut c_char {
|
||||
log::debug!("fxa_retry_migrate_from_session_token");
|
||||
ACCOUNTS.call_with_result_mut(error, handle, |fxa| -> fxa_client::Result<String> {
|
||||
let migration_metrics = fxa.try_migration()?;
|
||||
let result = serde_json::to_string(&migration_metrics)?;
|
||||
Ok(result)
|
||||
})
|
||||
}
|
||||
|
||||
/// Try to get an access token.
|
||||
///
|
||||
/// If the system can't find a suitable token but has a `refresh token` or a `session_token`,
|
||||
/// it will generate a new one on the go.
|
||||
///
|
||||
/// If not, the caller must start an OAuth flow with [fxa_begin_oauth_flow].
|
||||
///
|
||||
/// Arguments:
|
||||
/// * scope: space-separated list of scopes that the token should have
|
||||
/// * ttl: the time in seconds for which the token should be valid
|
||||
/// (or zero to use the server-controlled default ttl)
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// A destructor [fxa_bytebuffer_free] is provided for releasing the memory for this
|
||||
/// pointer type.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fxa_get_access_token(
|
||||
handle: u64,
|
||||
scope: FfiStr<'_>,
|
||||
ttl: u64,
|
||||
error: &mut ExternError,
|
||||
) -> ByteBuffer {
|
||||
log::debug!("fxa_get_access_token");
|
||||
ACCOUNTS.call_with_result_mut(error, handle, |fxa| {
|
||||
let scope = scope.as_str();
|
||||
let time_left = if ttl > 0 { Some(ttl) } else { None };
|
||||
fxa.get_access_token(scope, time_left)
|
||||
})
|
||||
}
|
||||
|
||||
/// Try to get a session token.
|
||||
///
|
||||
/// If the system can't find a suitable token it will return an error
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// A destructor [fxa_bytebuffer_free] is provided for releasing the memory for this
|
||||
/// pointer type.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fxa_get_session_token(handle: u64, error: &mut ExternError) -> *mut c_char {
|
||||
log::debug!("fxa_get_session_token");
|
||||
ACCOUNTS.call_with_result_mut(error, handle, |fxa| fxa.get_session_token())
|
||||
}
|
||||
|
||||
/// Retrieve the refresh token authorization status.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fxa_check_authorization_status(
|
||||
handle: u64,
|
||||
error: &mut ExternError,
|
||||
) -> ByteBuffer {
|
||||
log::debug!("fxa_check_authorization_status");
|
||||
ACCOUNTS.call_with_result_mut(error, handle, |fxa| fxa.check_authorization_status())
|
||||
}
|
||||
|
||||
/// This method should be called when a request made with
|
||||
/// an OAuth token failed with an authentication error.
|
||||
/// It clears the internal cache of OAuth access tokens,
|
||||
/// so the caller can try to call `fxa_get_access_token` or `fxa_profile`
|
||||
/// again.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fxa_clear_access_token_cache(handle: u64, error: &mut ExternError) {
|
||||
log::debug!("fxa_clear_access_token_cache");
|
||||
ACCOUNTS.call_with_output_mut(error, handle, |fxa| fxa.clear_access_token_cache())
|
||||
}
|
||||
|
||||
/// Try to get the current device id from state.
|
||||
///
|
||||
/// If the system can't find it then it will return an error
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// A destructor [fxa_bytebuffer_free] is provided for releasing the memory for this
|
||||
/// pointer type.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fxa_get_current_device_id(handle: u64, error: &mut ExternError) -> *mut c_char {
|
||||
log::debug!("fxa_get_current_device_id");
|
||||
ACCOUNTS.call_with_result_mut(error, handle, |fxa| fxa.get_current_device_id())
|
||||
}
|
||||
|
||||
/// Update the Push subscription information for the current device.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fxa_set_push_subscription(
|
||||
handle: u64,
|
||||
endpoint: FfiStr<'_>,
|
||||
public_key: FfiStr<'_>,
|
||||
auth_key: FfiStr<'_>,
|
||||
error: &mut ExternError,
|
||||
) {
|
||||
log::debug!("fxa_set_push_subscription");
|
||||
ACCOUNTS.call_with_result_mut(error, handle, |fxa| {
|
||||
let ps = PushSubscription {
|
||||
endpoint: endpoint.into_string(),
|
||||
public_key: public_key.into_string(),
|
||||
auth_key: auth_key.into_string(),
|
||||
};
|
||||
// We don't really care about passing back the resulting Device record.
|
||||
// We might in the future though.
|
||||
fxa.set_push_subscription(&ps).map(|_| ())
|
||||
})
|
||||
}
|
||||
|
||||
/// Update the display name for the current device.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fxa_set_device_name(
|
||||
handle: u64,
|
||||
display_name: FfiStr<'_>,
|
||||
error: &mut ExternError,
|
||||
) {
|
||||
log::debug!("fxa_set_device_name");
|
||||
ACCOUNTS.call_with_result_mut(error, handle, |fxa| {
|
||||
// We don't really care about passing back the resulting Device record.
|
||||
// We might in the future though.
|
||||
fxa.set_device_name(display_name.as_str()).map(|_| ())
|
||||
})
|
||||
}
|
||||
|
||||
/// Fetch the devices (including the current one) in the current account.
|
||||
///
|
||||
/// Devices might get cached in-memory and the caller might get served a cached version.
|
||||
/// To bypass this, the `ignore_cache` parameter can be set to `true`.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// A destructor [fxa_bytebuffer_free] is provided for releasing the memory for this
|
||||
/// pointer type.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fxa_get_devices(
|
||||
handle: u64,
|
||||
ignore_cache: u8,
|
||||
error: &mut ExternError,
|
||||
) -> ByteBuffer {
|
||||
log::debug!("fxa_get_devices");
|
||||
ACCOUNTS.call_with_result_mut(error, handle, |fxa| {
|
||||
fxa.get_devices(ignore_cache != 0).map(|d| {
|
||||
let devices = d.into_iter().map(|device| device.into()).collect();
|
||||
fxa_client::msg_types::Devices { devices }
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Try to get an OAuth code using a session token.
|
||||
///
|
||||
/// The system will use the stored `session_token` to provision a new code and return it.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// A destructor [fxa_bytebuffer_free] is provided for releasing the memory for this
|
||||
/// pointer type.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn fxa_authorize_auth_code(
|
||||
handle: u64,
|
||||
auth_params: *const u8,
|
||||
auth_params_len: i32,
|
||||
error: &mut ExternError,
|
||||
) -> *mut c_char {
|
||||
log::debug!("fxa_authorize_auth_code");
|
||||
ACCOUNTS.call_with_result_mut(error, handle, |fxa| {
|
||||
let auth_params = from_protobuf_ptr::<
|
||||
AuthorizationParameters,
|
||||
msg_types::AuthorizationParams,
|
||||
>(auth_params, auth_params_len)?;
|
||||
fxa.authorize_code_using_session_token(auth_params)
|
||||
})
|
||||
}
|
||||
|
||||
/// Typically called during a password change flow.
|
||||
/// Invalidate all tokens and get a new refresh token.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fxa_handle_session_token_change(
|
||||
handle: u64,
|
||||
new_session_token: FfiStr<'_>,
|
||||
error: &mut ExternError,
|
||||
) {
|
||||
log::debug!("fxa_handle_session_token_change");
|
||||
ACCOUNTS.call_with_result_mut(error, handle, |fxa| {
|
||||
let new_session_token = new_session_token.as_str();
|
||||
fxa.handle_session_token_change(new_session_token)
|
||||
})
|
||||
}
|
||||
|
||||
/// Poll and parse available remote commands targeted to our own device.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// A destructor [fxa_bytebuffer_free] is provided for releasing the memory for this
|
||||
/// pointer type.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fxa_poll_device_commands(handle: u64, error: &mut ExternError) -> ByteBuffer {
|
||||
log::debug!("fxa_poll_device_commands");
|
||||
ACCOUNTS.call_with_result_mut(error, handle, |fxa| {
|
||||
fxa.poll_device_commands(CommandFetchReason::Poll)
|
||||
.map(|cmds| {
|
||||
let commands = cmds.into_iter().map(|e| e.into()).collect();
|
||||
fxa_client::msg_types::IncomingDeviceCommands { commands }
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Disconnect from the account and optionaly destroy our device record.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fxa_disconnect(handle: u64, error: &mut ExternError) {
|
||||
log::debug!("fxa_disconnect");
|
||||
ACCOUNTS.call_with_result_mut(error, handle, |fxa| -> fxa_client::Result<()> {
|
||||
fxa.disconnect();
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// Handle a push payload coming from the Firefox Account servers.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// A destructor [fxa_bytebuffer_free] is provided for releasing the memory for this
|
||||
/// pointer type.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fxa_handle_push_message(
|
||||
handle: u64,
|
||||
json_payload: FfiStr<'_>,
|
||||
error: &mut ExternError,
|
||||
) -> ByteBuffer {
|
||||
log::debug!("fxa_handle_push_message");
|
||||
ACCOUNTS.call_with_result_mut(error, handle, |fxa| {
|
||||
fxa.handle_push_message(json_payload.as_str()).map(|evs| {
|
||||
let events = evs.into_iter().map(|e| e.into()).collect();
|
||||
fxa_client::msg_types::AccountEvents { events }
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Initalizes our own device, most of the time this will be called right after
|
||||
/// logging-in for the first time.
|
||||
///
|
||||
/// # Safety
|
||||
/// This function is unsafe because it will dereference `capabilities_data` and
|
||||
/// read `capabilities_len` bytes from it.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn fxa_initialize_device(
|
||||
handle: u64,
|
||||
name: FfiStr<'_>,
|
||||
device_type: i32,
|
||||
capabilities_data: *const u8,
|
||||
capabilities_len: i32,
|
||||
error: &mut ExternError,
|
||||
) {
|
||||
log::debug!("fxa_initialize_device");
|
||||
ACCOUNTS.call_with_result_mut(error, handle, |fxa| {
|
||||
let capabilities =
|
||||
DeviceCapability::from_protobuf_array_ptr(capabilities_data, capabilities_len)?;
|
||||
// This should not fail as device_type i32 representation is derived from our .proto schema.
|
||||
let device_type =
|
||||
msg_types::device::Type::from_i32(device_type).expect("Unknown device type code");
|
||||
fxa.initialize_device(name.as_str(), device_type.into(), &capabilities)
|
||||
})
|
||||
}
|
||||
|
||||
/// Ensure that the device capabilities are registered with the server.
|
||||
///
|
||||
/// # Safety
|
||||
/// This function is unsafe because it will dereference `capabilities_data` and
|
||||
/// read `capabilities_len` bytes from it.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn fxa_ensure_capabilities(
|
||||
handle: u64,
|
||||
capabilities_data: *const u8,
|
||||
capabilities_len: i32,
|
||||
error: &mut ExternError,
|
||||
) {
|
||||
log::debug!("fxa_ensure_capabilities");
|
||||
ACCOUNTS.call_with_result_mut(error, handle, |fxa| {
|
||||
let capabilities =
|
||||
DeviceCapability::from_protobuf_array_ptr(capabilities_data, capabilities_len)?;
|
||||
fxa.ensure_capabilities(&capabilities)
|
||||
})
|
||||
}
|
||||
|
||||
/// Send a tab to another device identified by its Device ID.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fxa_send_tab(
|
||||
handle: u64,
|
||||
target_device_id: FfiStr<'_>,
|
||||
title: FfiStr<'_>,
|
||||
url: FfiStr<'_>,
|
||||
error: &mut ExternError,
|
||||
) {
|
||||
log::debug!("fxa_send_tab");
|
||||
let target = target_device_id.as_str();
|
||||
let title = title.as_str();
|
||||
let url = url.as_str();
|
||||
ACCOUNTS.call_with_result_mut(error, handle, |fxa| fxa.send_tab(target, title, url))
|
||||
}
|
||||
|
||||
define_handle_map_deleter!(ACCOUNTS, fxa_free);
|
||||
define_string_destructor!(fxa_str_free);
|
||||
define_bytebuffer_destructor!(fxa_bytebuffer_free);
|
||||
|
||||
/// Gather telemetry collected by FxA.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fxa_gather_telemetry(handle: u64, error: &mut ExternError) -> *mut c_char {
|
||||
log::debug!("fxa_gather_telemetry");
|
||||
ACCOUNTS.call_with_result_mut(error, handle, |fxa| fxa.gather_telemetry())
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum FirefoxAccountError: LocalizedError {
|
||||
case unauthorized(message: String)
|
||||
case network(message: String)
|
||||
case unspecified(message: String)
|
||||
case panic(message: String)
|
||||
|
||||
// Trying to finish an authentication that was never started with begin(...)Flow.
|
||||
case noExistingAuthFlow
|
||||
// Trying to finish a different authentication flow.
|
||||
case wrongAuthFlow
|
||||
|
||||
/// Our implementation of the localizedError protocol -- (This shows up in Sentry)
|
||||
public var errorDescription: String? {
|
||||
switch self {
|
||||
case let .unauthorized(message):
|
||||
return "FirefoxAccountError.unauthorized: \(message)"
|
||||
case let .network(message):
|
||||
return "FirefoxAccountError.network: \(message)"
|
||||
case let .unspecified(message):
|
||||
return "FirefoxAccountError.unspecified: \(message)"
|
||||
case let .panic(message):
|
||||
return "FirefoxAccountError.panic: \(message)"
|
||||
case .noExistingAuthFlow:
|
||||
return "FirefoxAccountError.noExistingAuthFlow"
|
||||
case .wrongAuthFlow:
|
||||
return "FirefoxAccountError.wrongAuthFlow"
|
||||
}
|
||||
}
|
||||
|
||||
// The name is attempting to indicate that we free fxaError.message if it
|
||||
// existed, and that it's a very bad idea to touch it after you call this
|
||||
// function
|
||||
static func fromConsuming(_ rustError: FxAError) -> FirefoxAccountError? {
|
||||
let message = rustError.message
|
||||
switch rustError.code {
|
||||
case FxA_NoError:
|
||||
return nil
|
||||
case FxA_NetworkError:
|
||||
return .network(message: String(freeingFxaString: message!))
|
||||
case FxA_AuthenticationError:
|
||||
return .unauthorized(message: String(freeingFxaString: message!))
|
||||
case FxA_Other:
|
||||
return .unspecified(message: String(freeingFxaString: message!))
|
||||
case FxA_InternalPanic:
|
||||
return .panic(message: String(freeingFxaString: message!))
|
||||
default:
|
||||
return .unspecified(message: String(freeingFxaString: message!))
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public static func unwrap<T>(_ callback: (UnsafeMutablePointer<FxAError>) throws -> T?) throws -> T {
|
||||
guard let result = try tryUnwrap(callback) else {
|
||||
throw ResultError.empty
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public static func tryUnwrap<T>(_ callback: (UnsafeMutablePointer<FxAError>) throws -> T?) throws -> T? {
|
||||
var err = FxAError(code: FxA_NoError, message: nil)
|
||||
let returnedVal = try callback(&err)
|
||||
if let fxaErr = FirefoxAccountError.fromConsuming(err) {
|
||||
throw fxaErr
|
||||
}
|
||||
return returnedVal
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Data {
|
||||
init(rustBuffer: FxARustBuffer) {
|
||||
self.init(bytes: rustBuffer.data!, count: Int(rustBuffer.len))
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import Foundation
|
||||
|
||||
extension String {
|
||||
init(freeingFxaString fxaString: UnsafeMutablePointer<CChar>) {
|
||||
defer { fxa_str_free(fxaString) }
|
||||
self.init(cString: fxaString)
|
||||
}
|
||||
}
|
|
@ -1,129 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import Foundation
|
||||
|
||||
/// This class provides low-level access to `RustFxAccount` through various asynchronous wrappers.
|
||||
/// It should not be used anymore and is kept for backwards compatbility for the Lockwise iOS project.
|
||||
@available(*, deprecated, message: "Use FxAccountManager instead")
|
||||
open class FirefoxAccount: RustFxAccount {
|
||||
/// Gets the logged-in user profile.
|
||||
/// Throws `FirefoxAccountError.Unauthorized` if we couldn't find any suitable access token
|
||||
/// to make that call. The caller should then start the OAuth Flow again with
|
||||
/// the "profile" scope.
|
||||
open func getProfile(completionHandler: @escaping (Profile?, Error?) -> Void) {
|
||||
DispatchQueue.global().async {
|
||||
do {
|
||||
let profile = try super.getProfile(ignoreCache: false)
|
||||
DispatchQueue.main.async { completionHandler(profile, nil) }
|
||||
} catch {
|
||||
DispatchQueue.main.async { completionHandler(nil, error) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Request a OAuth token by starting a new OAuth flow.
|
||||
///
|
||||
/// This function returns a URL string that the caller should open in a webview.
|
||||
///
|
||||
/// Once the user has confirmed the authorization grant, they will get redirected to `redirect_url`:
|
||||
/// the caller must intercept that redirection, extract the `code` and `state` query parameters and call
|
||||
/// `completeOAuthFlow(...)` to complete the flow.
|
||||
open func beginOAuthFlow(
|
||||
scopes: [String],
|
||||
entrypoint: String,
|
||||
metricsParams: MetricsParams = MetricsParams.newEmpty(),
|
||||
completionHandler: @escaping (URL?, Error?) -> Void
|
||||
) {
|
||||
DispatchQueue.global().async {
|
||||
do {
|
||||
let url = try super.beginOAuthFlow(
|
||||
scopes: scopes,
|
||||
entrypoint: entrypoint,
|
||||
metricsParams: metricsParams
|
||||
)
|
||||
DispatchQueue.main.async { completionHandler(url, nil) }
|
||||
} catch {
|
||||
DispatchQueue.main.async { completionHandler(nil, error) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Finish an OAuth flow initiated by `beginOAuthFlow(...)` and returns token/keys.
|
||||
///
|
||||
/// This resulting token might not have all the `scopes` the caller have requested (e.g. the user
|
||||
/// might have denied some of them): it is the responsibility of the caller to accomodate that.
|
||||
open func completeOAuthFlow(code: String, state: String, completionHandler: @escaping (Void, Error?) -> Void) {
|
||||
DispatchQueue.global().async {
|
||||
do {
|
||||
try super.completeOAuthFlow(code: code, state: state)
|
||||
DispatchQueue.main.async { completionHandler((), nil) }
|
||||
} catch {
|
||||
DispatchQueue.main.async { completionHandler((), error) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to get an OAuth access token.
|
||||
///
|
||||
/// `ttl` corresponds to the time in seconds for which the token will be used.
|
||||
/// Throws `FirefoxAccountError.Unauthorized` if we couldn't provide an access token
|
||||
/// for this scope. The caller should then start the OAuth Flow again with
|
||||
/// the desired scope.
|
||||
open func getAccessToken(
|
||||
scope: String,
|
||||
ttl: UInt64? = nil,
|
||||
completionHandler: @escaping (AccessTokenInfo?, Error?) -> Void
|
||||
) {
|
||||
DispatchQueue.global().async {
|
||||
do {
|
||||
let tokenInfo = try super.getAccessToken(scope: scope, ttl: ttl)
|
||||
DispatchQueue.main.async { completionHandler(tokenInfo, nil) }
|
||||
} catch {
|
||||
DispatchQueue.main.async { completionHandler(nil, error) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether the refreshToken is active
|
||||
open func checkAuthorizationStatus(completionHandler: @escaping (IntrospectInfo?, Error?) -> Void) {
|
||||
DispatchQueue.global().async {
|
||||
do {
|
||||
let tokenInfo = try super.checkAuthorizationStatus()
|
||||
DispatchQueue.main.async { completionHandler(tokenInfo, nil) }
|
||||
} catch {
|
||||
DispatchQueue.main.async { completionHandler(nil, error) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This method should be called when a request made with
|
||||
/// an OAuth token failed with an authentication error.
|
||||
/// It clears the internal cache of OAuth access tokens,
|
||||
/// so the caller can try to call `getAccessToken` or `getProfile`
|
||||
/// again.
|
||||
open func clearAccessTokenCache(completionHandler: @escaping (Void, Error?) -> Void) {
|
||||
DispatchQueue.global().async {
|
||||
do {
|
||||
try super.clearAccessTokenCache()
|
||||
DispatchQueue.main.async { completionHandler((), nil) }
|
||||
} catch {
|
||||
DispatchQueue.main.async { completionHandler((), error) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Disconnect from the account and optionaly destroy our device record.
|
||||
/// `beginOAuthFlow(...)` will need to be called to reconnect.
|
||||
open func disconnect(completionHandler: @escaping (Void, Error?) -> Void) {
|
||||
DispatchQueue.global().async {
|
||||
do {
|
||||
try super.disconnect()
|
||||
DispatchQueue.main.async { completionHandler((), nil) }
|
||||
} catch {
|
||||
DispatchQueue.main.async { completionHandler((), error) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,226 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import Foundation
|
||||
|
||||
/// This class inherits from `RustFxAccount` and adds:
|
||||
/// - Automatic state persistence through `PersistCallback`.
|
||||
/// - Auth error signaling through observer notifications.
|
||||
class FxAccount: RustFxAccount {
|
||||
private var persistCallback: PersistCallback?
|
||||
|
||||
/// Registers a persistance callback. The callback will get called every time
|
||||
/// the `FxAccounts` state needs to be saved. The callback must
|
||||
/// persist the passed string in a secure location (like the keychain).
|
||||
public func registerPersistCallback(_ cb: PersistCallback) {
|
||||
persistCallback = cb
|
||||
}
|
||||
|
||||
/// Unregisters a persistance callback.
|
||||
public func unregisterPersistCallback() {
|
||||
persistCallback = nil
|
||||
}
|
||||
|
||||
override func getProfile(ignoreCache: Bool) throws -> Profile {
|
||||
defer { tryPersistState() }
|
||||
return try notifyAuthErrors {
|
||||
try super.getProfile(ignoreCache: ignoreCache)
|
||||
}
|
||||
}
|
||||
|
||||
override func beginOAuthFlow(
|
||||
scopes: [String],
|
||||
entrypoint: String,
|
||||
metricsParams: MetricsParams = MetricsParams.newEmpty()
|
||||
) throws -> URL {
|
||||
return try notifyAuthErrors {
|
||||
try super.beginOAuthFlow(
|
||||
scopes: scopes,
|
||||
entrypoint: entrypoint,
|
||||
metricsParams: metricsParams
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override func beginPairingFlow(
|
||||
pairingUrl: String,
|
||||
scopes: [String],
|
||||
entrypoint: String,
|
||||
metricsParams: MetricsParams = MetricsParams.newEmpty()
|
||||
) throws -> URL {
|
||||
return try notifyAuthErrors {
|
||||
try super.beginPairingFlow(
|
||||
pairingUrl: pairingUrl,
|
||||
scopes: scopes,
|
||||
entrypoint: entrypoint,
|
||||
metricsParams: metricsParams
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override func completeOAuthFlow(code: String, state: String) throws {
|
||||
defer { tryPersistState() }
|
||||
try notifyAuthErrors {
|
||||
try super.completeOAuthFlow(code: code, state: state)
|
||||
}
|
||||
}
|
||||
|
||||
override func getAccessToken(scope: String, ttl: UInt64? = nil) throws -> AccessTokenInfo {
|
||||
defer { tryPersistState() }
|
||||
return try notifyAuthErrors {
|
||||
try super.getAccessToken(scope: scope, ttl: ttl)
|
||||
}
|
||||
}
|
||||
|
||||
override func disconnect() throws {
|
||||
defer { tryPersistState() }
|
||||
try super.disconnect()
|
||||
}
|
||||
|
||||
override func pollDeviceCommands() throws -> [IncomingDeviceCommand] {
|
||||
defer { tryPersistState() }
|
||||
return try notifyAuthErrors {
|
||||
try super.pollDeviceCommands()
|
||||
}
|
||||
}
|
||||
|
||||
override func getDevices(ignoreCache: Bool = false) throws -> [Device] {
|
||||
return try notifyAuthErrors {
|
||||
try super.getDevices(ignoreCache: ignoreCache)
|
||||
}
|
||||
}
|
||||
|
||||
override func setDevicePushSubscription(endpoint: String, publicKey: String, authKey: String) throws {
|
||||
try notifyAuthErrors {
|
||||
try super.setDevicePushSubscription(endpoint: endpoint, publicKey: publicKey, authKey: authKey)
|
||||
}
|
||||
}
|
||||
|
||||
override func setDeviceDisplayName(_ name: String) throws {
|
||||
try notifyAuthErrors {
|
||||
try super.setDeviceDisplayName(name)
|
||||
}
|
||||
}
|
||||
|
||||
override func handlePushMessage(payload: String) throws -> [AccountEvent] {
|
||||
defer { tryPersistState() }
|
||||
return try notifyAuthErrors {
|
||||
try super.handlePushMessage(payload: payload)
|
||||
}
|
||||
}
|
||||
|
||||
override func sendSingleTab(targetId: String, title: String, url: String) throws {
|
||||
return try notifyAuthErrors {
|
||||
try super.sendSingleTab(targetId: targetId, title: title, url: url)
|
||||
}
|
||||
}
|
||||
|
||||
override func initializeDevice(
|
||||
name: String,
|
||||
deviceType: DeviceType,
|
||||
supportedCapabilities: [DeviceCapability]
|
||||
) throws {
|
||||
defer { tryPersistState() }
|
||||
try notifyAuthErrors {
|
||||
try super.initializeDevice(name: name, deviceType: deviceType, supportedCapabilities: supportedCapabilities)
|
||||
}
|
||||
}
|
||||
|
||||
override func ensureCapabilities(supportedCapabilities: [DeviceCapability]) throws {
|
||||
defer { tryPersistState() }
|
||||
try notifyAuthErrors {
|
||||
try super.ensureCapabilities(supportedCapabilities: supportedCapabilities)
|
||||
}
|
||||
}
|
||||
|
||||
override func migrateFromSessionToken(sessionToken: String, kSync: String, kXCS: String) -> Bool {
|
||||
defer { tryPersistState() }
|
||||
do {
|
||||
return try super.migrateFromSessionToken(sessionToken: sessionToken, kSync: kSync, kXCS: kXCS)
|
||||
} catch {
|
||||
FxALog.error("migrateFromSessionToken error: \(error).")
|
||||
reportAccountMigrationError(error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
override func retryMigrateFromSessionToken() -> Bool {
|
||||
defer { tryPersistState() }
|
||||
do {
|
||||
return try super.retryMigrateFromSessionToken()
|
||||
} catch {
|
||||
FxALog.error("retryMigrateFromSessionToken error: \(error).")
|
||||
reportAccountMigrationError(error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
override func handleSessionTokenChange(sessionToken: String) throws {
|
||||
defer { tryPersistState() }
|
||||
try notifyAuthErrors {
|
||||
try super.handleSessionTokenChange(sessionToken: sessionToken)
|
||||
}
|
||||
}
|
||||
|
||||
internal func reportAccountMigrationError(_ error: Error) {
|
||||
// Not in migration state after throwing during migration = unrecoverable error.
|
||||
if !isInMigrationState() {
|
||||
DispatchQueue.main.async {
|
||||
NotificationCenter.default.post(
|
||||
name: .accountMigrationFailed,
|
||||
object: nil,
|
||||
userInfo: ["error": error]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func isInMigrationState() -> Bool {
|
||||
do {
|
||||
return try super.isInMigrationState()
|
||||
} catch {
|
||||
FxALog.error("isInMigrationState error: \(error).")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
override func clearAccessTokenCache() throws {
|
||||
defer { tryPersistState() }
|
||||
try super.clearAccessTokenCache()
|
||||
}
|
||||
|
||||
private func tryPersistState() {
|
||||
guard let cb = persistCallback else {
|
||||
return
|
||||
}
|
||||
do {
|
||||
let json = try toJSON()
|
||||
cb.persist(json: json)
|
||||
} catch {
|
||||
// Ignore the error because the prior operation might have worked,
|
||||
// but still log it.
|
||||
FxALog.error("FxAccounts internal state serialization failed.")
|
||||
}
|
||||
}
|
||||
|
||||
internal func notifyAuthErrors<T>(_ cb: () throws -> T) rethrows -> T {
|
||||
do {
|
||||
return try cb()
|
||||
} catch let error as FirefoxAccountError {
|
||||
if case let .unauthorized(msg) = error {
|
||||
FxALog.debug("Auth error caught: \(msg)")
|
||||
notifyAuthError()
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
internal func notifyAuthError() {
|
||||
NotificationCenter.default.post(name: .accountAuthException, object: nil)
|
||||
}
|
||||
}
|
||||
|
||||
public protocol PersistCallback {
|
||||
func persist(json: String)
|
||||
}
|
|
@ -1,163 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct Device {
|
||||
public let id: String
|
||||
public let displayName: String
|
||||
public let deviceType: DeviceType
|
||||
public let isCurrentDevice: Bool
|
||||
public let lastAccessTime: UInt64?
|
||||
public let capabilities: [DeviceCapability]
|
||||
public let subscriptionExpired: Bool
|
||||
public let subscription: DevicePushSubscription?
|
||||
|
||||
internal static func fromCollectionMsg(msg: MsgTypes_Devices) -> [Device] {
|
||||
msg.devices.map { Device(msg: $0) }
|
||||
}
|
||||
|
||||
internal init(msg: MsgTypes_Device) {
|
||||
id = msg.id
|
||||
displayName = msg.displayName
|
||||
deviceType = DeviceType.fromMsg(msg: msg.type)
|
||||
isCurrentDevice = msg.isCurrentDevice
|
||||
lastAccessTime = msg.hasLastAccessTime ? msg.lastAccessTime : nil
|
||||
capabilities = msg.capabilities.map { DeviceCapability.fromMsg(msg: $0) }
|
||||
subscriptionExpired = msg.pushEndpointExpired
|
||||
subscription = msg.hasPushSubscription ?
|
||||
DevicePushSubscription(msg: msg.pushSubscription) :
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
||||
public enum DeviceType {
|
||||
case desktop
|
||||
case mobile
|
||||
case tablet
|
||||
case tv
|
||||
case vr
|
||||
case unknown
|
||||
|
||||
internal static func fromMsg(msg: MsgTypes_Device.TypeEnum) -> DeviceType {
|
||||
switch msg {
|
||||
case .desktop: return .desktop
|
||||
case .mobile: return .mobile
|
||||
case .tablet: return .tablet
|
||||
case .tv: return .tv
|
||||
case .vr: return .vr
|
||||
case .unknown: return .unknown
|
||||
}
|
||||
}
|
||||
|
||||
internal func toMsg() -> MsgTypes_Device.TypeEnum {
|
||||
switch self {
|
||||
case .desktop: return .desktop
|
||||
case .mobile: return .mobile
|
||||
case .tablet: return .tablet
|
||||
case .tv: return .tv
|
||||
case .vr: return .vr
|
||||
case .unknown: return .unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum DeviceCapability {
|
||||
case sendTab
|
||||
|
||||
internal static func fromMsg(msg: MsgTypes_Device.Capability) -> DeviceCapability {
|
||||
switch msg {
|
||||
case .sendTab: return .sendTab
|
||||
}
|
||||
}
|
||||
|
||||
internal func toMsg() -> MsgTypes_Device.Capability {
|
||||
switch self {
|
||||
case .sendTab: return .sendTab
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Array where Element == DeviceCapability {
|
||||
func toCollectionMsg() -> MsgTypes_Capabilities {
|
||||
MsgTypes_Capabilities.with {
|
||||
$0.capability = self.map { $0.toMsg() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct DevicePushSubscription {
|
||||
public let endpoint: String
|
||||
public let publicKey: String
|
||||
public let authKey: String
|
||||
|
||||
public init(endpoint: String, publicKey: String, authKey: String) {
|
||||
self.endpoint = endpoint
|
||||
self.publicKey = publicKey
|
||||
self.authKey = authKey
|
||||
}
|
||||
|
||||
internal init(msg: MsgTypes_Device.PushSubscription) {
|
||||
endpoint = msg.endpoint
|
||||
publicKey = msg.publicKey
|
||||
authKey = msg.authKey
|
||||
}
|
||||
}
|
||||
|
||||
public enum IncomingDeviceCommand {
|
||||
case tabReceived(Device?, [TabData])
|
||||
|
||||
internal static func fromCollectionMsg(msg: MsgTypes_IncomingDeviceCommands) -> [IncomingDeviceCommand] {
|
||||
msg.commands.map { IncomingDeviceCommand.fromMsg(msg: $0) }
|
||||
}
|
||||
|
||||
internal static func fromMsg(msg: MsgTypes_IncomingDeviceCommand) -> IncomingDeviceCommand {
|
||||
switch msg.type {
|
||||
case .tabReceived: do {
|
||||
let data = msg.tabReceivedData
|
||||
let device = data.hasFrom ? Device(msg: data.from) : nil
|
||||
let entries = data.entries.map { TabData(title: $0.title, url: $0.url) }
|
||||
return .tabReceived(device, entries)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum AccountEvent {
|
||||
case incomingDeviceCommand(IncomingDeviceCommand)
|
||||
case deviceConnected(deviceName: String)
|
||||
case deviceDisconnected(deviceId: String, isLocalDevice: Bool)
|
||||
|
||||
internal static func fromCollectionMsg(msg: MsgTypes_AccountEvents) -> [AccountEvent] {
|
||||
msg.events.compactMap { AccountEvent.fromMsg(msg: $0) }
|
||||
}
|
||||
|
||||
internal static func fromMsg(msg: MsgTypes_AccountEvent) -> AccountEvent? {
|
||||
switch msg.type {
|
||||
case .incomingDeviceCommand: do {
|
||||
return .incomingDeviceCommand(IncomingDeviceCommand.fromMsg(msg: msg.deviceCommand))
|
||||
}
|
||||
case .deviceConnected: do {
|
||||
return .deviceConnected(deviceName: msg.deviceConnectedName)
|
||||
}
|
||||
case .deviceDisconnected: do {
|
||||
return .deviceDisconnected(
|
||||
deviceId: msg.deviceDisconnectedData.deviceID,
|
||||
isLocalDevice: msg.deviceDisconnectedData.isLocalDevice
|
||||
)
|
||||
}
|
||||
// The following push messages are filtered upstream by the FxA server,
|
||||
// because iOS requires all Push messages to show a UI notification to the user
|
||||
// and in these cases it was deemed not useful.
|
||||
case .profileUpdated: return nil
|
||||
case .accountAuthStateChanged: return nil
|
||||
case .accountDestroyed: return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct TabData {
|
||||
public let title: String
|
||||
public let url: String
|
||||
}
|
|
@ -15,9 +15,9 @@ public struct ConstellationState {
|
|||
|
||||
public class DeviceConstellation {
|
||||
var constellationState: ConstellationState?
|
||||
let account: FxAccount
|
||||
let account: PersistedFirefoxAccount
|
||||
|
||||
init(account: FxAccount) {
|
||||
init(account: PersistedFirefoxAccount) {
|
||||
self.account = account
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ public class DeviceConstellation {
|
|||
do {
|
||||
let devices = try self.account.getDevices(ignoreCache: true)
|
||||
let localDevice = devices.first { $0.isCurrentDevice }
|
||||
if localDevice?.subscriptionExpired ?? false {
|
||||
if localDevice?.pushEndpointExpired ?? false {
|
||||
FxALog.debug("Current device needs push endpoint registration.")
|
||||
}
|
||||
let remoteDevices = devices.filter { !$0.isCurrentDevice }
|
||||
|
@ -64,7 +64,7 @@ public class DeviceConstellation {
|
|||
public func setLocalDeviceName(name: String) {
|
||||
DispatchQueue.global().async {
|
||||
do {
|
||||
try self.account.setDeviceDisplayName(name)
|
||||
try self.account.setDeviceName(name)
|
||||
// Update our list of devices in the background to reflect the change.
|
||||
self.refreshState()
|
||||
} catch {
|
||||
|
@ -92,7 +92,7 @@ public class DeviceConstellation {
|
|||
do {
|
||||
switch e {
|
||||
case let .sendTab(title, url): do {
|
||||
try self.account.sendSingleTab(targetId: targetDeviceId, title: title, url: url)
|
||||
try self.account.sendSingleTab(targetDeviceId: targetDeviceId, title: title, url: url)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
|
@ -105,11 +105,7 @@ public class DeviceConstellation {
|
|||
public func setDevicePushSubscription(sub: DevicePushSubscription) {
|
||||
DispatchQueue.global().async {
|
||||
do {
|
||||
try self.account.setDevicePushSubscription(
|
||||
endpoint: sub.endpoint,
|
||||
publicKey: sub.publicKey,
|
||||
authKey: sub.authKey
|
||||
)
|
||||
try self.account.setDevicePushSubscription(sub: sub)
|
||||
} catch {
|
||||
FxALog.error("Failure setting push subscription: \(error).")
|
||||
}
|
||||
|
|
|
@ -19,8 +19,8 @@ open class FxAccountManager {
|
|||
var deviceConfig: DeviceConfig
|
||||
let applicationScopes: [String]
|
||||
|
||||
var acct: FxAccount?
|
||||
var account: FxAccount? {
|
||||
var acct: PersistedFirefoxAccount?
|
||||
var account: PersistedFirefoxAccount? {
|
||||
get { return acct }
|
||||
set {
|
||||
acct = newValue
|
||||
|
@ -95,7 +95,7 @@ open class FxAccountManager {
|
|||
/// `finishAuthentication(...)` to complete the flow.
|
||||
public func beginAuthentication(
|
||||
entrypoint: String,
|
||||
metricsParams: MetricsParams = MetricsParams.newEmpty(),
|
||||
metrics: MetricsParams = MetricsParams(parameters: [:]),
|
||||
completionHandler: @escaping (Result<URL, Error>) -> Void
|
||||
) {
|
||||
FxALog.info("beginAuthentication")
|
||||
|
@ -104,7 +104,7 @@ open class FxAccountManager {
|
|||
try account.beginOAuthFlow(
|
||||
scopes: self.applicationScopes,
|
||||
entrypoint: entrypoint,
|
||||
metricsParams: metricsParams
|
||||
metrics: metrics
|
||||
)
|
||||
}
|
||||
DispatchQueue.main.async { completionHandler(result) }
|
||||
|
@ -123,7 +123,7 @@ open class FxAccountManager {
|
|||
public func beginPairingAuthentication(
|
||||
pairingUrl: String,
|
||||
entrypoint: String,
|
||||
metricsParams: MetricsParams = MetricsParams.newEmpty(),
|
||||
metrics: MetricsParams = MetricsParams(parameters: [:]),
|
||||
completionHandler: @escaping (Result<URL, Error>) -> Void
|
||||
) {
|
||||
DispatchQueue.global().async {
|
||||
|
@ -132,7 +132,7 @@ open class FxAccountManager {
|
|||
pairingUrl: pairingUrl,
|
||||
scopes: self.applicationScopes,
|
||||
entrypoint: entrypoint,
|
||||
metricsParams: metricsParams
|
||||
metrics: metrics
|
||||
)
|
||||
}
|
||||
DispatchQueue.main.async { completionHandler(result) }
|
||||
|
@ -143,7 +143,7 @@ open class FxAccountManager {
|
|||
/// and put it aside for later in `latestOAuthStateParam`.
|
||||
/// Afterwards, in `finishAuthentication` we ensure that we are
|
||||
/// finishing the correct (and same) authentication flow.
|
||||
private func updatingLatestAuthState(_ beginFlowFn: (FxAccount) throws -> URL) -> Result<URL, Error> {
|
||||
private func updatingLatestAuthState(_ beginFlowFn: (PersistedFirefoxAccount) throws -> URL) -> Result<URL, Error> {
|
||||
do {
|
||||
let url = try beginFlowFn(requireAccount())
|
||||
let comps = URLComponents(url: url, resolvingAgainstBaseURL: true)
|
||||
|
@ -194,9 +194,9 @@ open class FxAccountManager {
|
|||
completionHandler: @escaping (Result<Void, Error>) -> Void
|
||||
) {
|
||||
if latestOAuthStateParam == nil {
|
||||
DispatchQueue.main.async { completionHandler(.failure(FirefoxAccountError.noExistingAuthFlow)) }
|
||||
DispatchQueue.main.async { completionHandler(.failure(FxaError.NoExistingAuthFlow(message: ""))) }
|
||||
} else if authData.state != latestOAuthStateParam {
|
||||
DispatchQueue.main.async { completionHandler(.failure(FirefoxAccountError.wrongAuthFlow)) }
|
||||
DispatchQueue.main.async { completionHandler(.failure(FxaError.WrongAuthFlow(message: ""))) }
|
||||
} else { /* state == latestAuthState */
|
||||
processEvent(event: .authenticated(authData: authData)) {
|
||||
DispatchQueue.main.async { completionHandler(.success(())) }
|
||||
|
@ -378,12 +378,8 @@ open class FxAccountManager {
|
|||
switch via {
|
||||
case .logout: do {
|
||||
// Clean up internal account state and destroy the current FxA device record.
|
||||
do {
|
||||
try requireAccount().disconnect()
|
||||
FxALog.info("Disconnected FxA account")
|
||||
} catch {
|
||||
FxALog.error("Failed to fully disconnect the FxA account: \(error).")
|
||||
}
|
||||
requireAccount().disconnect()
|
||||
FxALog.info("Disconnected FxA account")
|
||||
profile = nil
|
||||
constellation = nil
|
||||
accountStorage.clear()
|
||||
|
@ -407,7 +403,8 @@ open class FxAccountManager {
|
|||
if acct.migrateFromSessionToken(
|
||||
sessionToken: sessionToken,
|
||||
kSync: kSync,
|
||||
kXCS: kXCS
|
||||
kXCS: kXCS,
|
||||
copySessionToken: false
|
||||
) {
|
||||
return .authenticatedViaMigration
|
||||
}
|
||||
|
@ -597,7 +594,7 @@ open class FxAccountManager {
|
|||
onError()
|
||||
return nil
|
||||
}
|
||||
try account.clearAccessTokenCache()
|
||||
account.clearAccessTokenCache()
|
||||
// Make sure we're back on track by re-requesting the profile access token.
|
||||
_ = try account.getAccessToken(scope: OAuthScope.profile)
|
||||
return .recoveredFromAuthenticationProblem
|
||||
|
@ -612,15 +609,15 @@ open class FxAccountManager {
|
|||
return nil
|
||||
}
|
||||
|
||||
internal func createAccount() -> FxAccount {
|
||||
return try! FxAccount(config: config)
|
||||
internal func createAccount() -> PersistedFirefoxAccount {
|
||||
return PersistedFirefoxAccount(config: config)
|
||||
}
|
||||
|
||||
internal func tryRestoreAccount() -> FxAccount? {
|
||||
internal func tryRestoreAccount() -> PersistedFirefoxAccount? {
|
||||
return accountStorage.read()
|
||||
}
|
||||
|
||||
internal func makeDeviceConstellation(account: FxAccount) -> DeviceConstellation {
|
||||
internal func makeDeviceConstellation(account: PersistedFirefoxAccount) -> DeviceConstellation {
|
||||
return DeviceConstellation(account: account)
|
||||
}
|
||||
|
||||
|
@ -657,7 +654,7 @@ open class FxAccountManager {
|
|||
}
|
||||
}
|
||||
|
||||
internal func requireAccount() -> FxAccount {
|
||||
internal func requireAccount() -> PersistedFirefoxAccount {
|
||||
if let acct = account {
|
||||
return acct
|
||||
}
|
||||
|
|
|
@ -1,72 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct MetricsParams {
|
||||
public let flowId: String?
|
||||
public let flowBeginTime: UInt64?
|
||||
public let deviceId: String?
|
||||
public let utmSource: String?
|
||||
public let utmContent: String?
|
||||
public let utmMedium: String?
|
||||
public let utmTerm: String?
|
||||
public let utmCampaign: String?
|
||||
public let entrypointExperiment: String?
|
||||
public let entrypointVariation: String?
|
||||
|
||||
internal func toMsg() -> MsgTypes_MetricsParams {
|
||||
var msg = MsgTypes_MetricsParams()
|
||||
var params = [String: String]()
|
||||
|
||||
if flowId != nil {
|
||||
params["flow_id"] = flowId
|
||||
}
|
||||
if flowBeginTime != nil {
|
||||
params["flow_begin_time"] = String(describing: flowBeginTime)
|
||||
}
|
||||
if deviceId != nil {
|
||||
params["device_id"] = deviceId
|
||||
}
|
||||
if utmSource != nil {
|
||||
params["utm_source"] = utmSource
|
||||
}
|
||||
if utmContent != nil {
|
||||
params["utm_content"] = utmContent
|
||||
}
|
||||
if utmMedium != nil {
|
||||
params["utm_medium"] = utmMedium
|
||||
}
|
||||
if utmTerm != nil {
|
||||
params["utm_term"] = flowId
|
||||
}
|
||||
if utmCampaign != nil {
|
||||
params["utm_campaign"] = utmCampaign
|
||||
}
|
||||
if entrypointExperiment != nil {
|
||||
params["entrypoint_experiment"] = entrypointExperiment
|
||||
}
|
||||
if entrypointVariation != nil {
|
||||
params["entrypoint_variation"] = entrypointVariation
|
||||
}
|
||||
|
||||
msg.parameters = params
|
||||
return msg
|
||||
}
|
||||
|
||||
public static func newEmpty() -> MetricsParams {
|
||||
return MetricsParams(
|
||||
flowId: nil,
|
||||
flowBeginTime: nil,
|
||||
deviceId: nil,
|
||||
utmSource: nil,
|
||||
utmContent: nil,
|
||||
utmMedium: nil,
|
||||
utmTerm: nil,
|
||||
utmCampaign: nil,
|
||||
entrypointExperiment: nil,
|
||||
entrypointVariation: nil
|
||||
)
|
||||
}
|
||||
}
|
|
@ -4,21 +4,6 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
enum MigrationState {
|
||||
case none
|
||||
case copySessionToken
|
||||
case reuseSessionToken
|
||||
|
||||
internal static func fromNumber(_ number: UInt8) -> MigrationState {
|
||||
switch number {
|
||||
case 0: return .none
|
||||
case 1: return .copySessionToken
|
||||
case 2: return .reuseSessionToken
|
||||
default: fatalError("Unreachable")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum MigrationResult {
|
||||
// Sign-in failed due to an intermittent problem (such as a network failure). A retry attempt will
|
||||
// be performed automatically during account manager initialization.
|
||||
|
|
|
@ -12,59 +12,3 @@ public enum OAuthScope {
|
|||
// Necessary to obtain a sessionToken, which gives full access to the account.
|
||||
public static let session: String = "https://identity.mozilla.com/tokens/session"
|
||||
}
|
||||
|
||||
public struct ScopedKey {
|
||||
public let kty: String
|
||||
public let scope: String
|
||||
public let k: String
|
||||
public let kid: String
|
||||
|
||||
internal init(msg: MsgTypes_ScopedKey) {
|
||||
kty = msg.kty
|
||||
scope = msg.scope
|
||||
k = msg.k
|
||||
kid = msg.kid
|
||||
}
|
||||
}
|
||||
|
||||
public struct AccessTokenInfo {
|
||||
public let scope: String
|
||||
public let token: String
|
||||
public let key: ScopedKey?
|
||||
public let expiresAt: Date
|
||||
|
||||
internal init(msg: MsgTypes_AccessTokenInfo) {
|
||||
scope = msg.scope
|
||||
token = msg.token
|
||||
key = msg.hasKey ? ScopedKey(msg: msg.key) : nil
|
||||
expiresAt = Date(timeIntervalSince1970: Double(msg.expiresAt))
|
||||
}
|
||||
|
||||
// For testing.
|
||||
internal init(
|
||||
scope: String,
|
||||
token: String,
|
||||
key: ScopedKey? = nil,
|
||||
expiresAt: Date = Date()
|
||||
) {
|
||||
self.scope = scope
|
||||
self.token = token
|
||||
self.key = key
|
||||
self.expiresAt = expiresAt
|
||||
}
|
||||
}
|
||||
|
||||
public struct IntrospectInfo {
|
||||
public let active: Bool
|
||||
|
||||
internal init(msg: MsgTypes_IntrospectInfo) {
|
||||
active = msg.active
|
||||
}
|
||||
|
||||
// For testing.
|
||||
internal init(
|
||||
active: Bool
|
||||
) {
|
||||
self.active = active
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct Avatar {
|
||||
public let url: String
|
||||
public let isDefault: Bool
|
||||
}
|
||||
|
||||
public struct Profile {
|
||||
public let uid: String
|
||||
public let email: String
|
||||
public let avatar: Avatar?
|
||||
public let displayName: String?
|
||||
|
||||
internal init(msg: MsgTypes_Profile) {
|
||||
uid = msg.uid
|
||||
email = msg.email
|
||||
avatar = msg.hasAvatar ? Avatar(url: msg.avatar, isDefault: msg.avatarDefault) : nil
|
||||
displayName = msg.hasDisplayName ? msg.displayName : nil
|
||||
}
|
||||
|
||||
// For testing.
|
||||
internal init(uid: String, email: String, avatar: Avatar? = nil, displayName: String? = nil) {
|
||||
self.uid = uid
|
||||
self.email = email
|
||||
self.avatar = avatar
|
||||
self.displayName = displayName
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@ class KeyChainAccountStorage {
|
|||
keychainWrapper = KeychainWrapper.sharedAppContainerKeychain(keychainAccessGroup: keychainAccessGroup)
|
||||
}
|
||||
|
||||
func read() -> FxAccount? {
|
||||
func read() -> PersistedFirefoxAccount? {
|
||||
// Firefox iOS v25.0 shipped with the default accessibility, which breaks Send Tab when the screen is locked.
|
||||
// This method migrates the existing keychains to the correct accessibility.
|
||||
keychainWrapper.ensureStringItemAccessibility(
|
||||
|
@ -26,7 +26,7 @@ class KeyChainAccountStorage {
|
|||
withAccessibility: KeyChainAccountStorage.accessibility
|
||||
) {
|
||||
do {
|
||||
return try FxAccount(fromJsonState: json)
|
||||
return try PersistedFirefoxAccount.fromJSON(data: json)
|
||||
} catch {
|
||||
FxALog.error("FxAccount internal state de-serialization failed: \(error).")
|
||||
return nil
|
||||
|
|
|
@ -0,0 +1,331 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import Foundation
|
||||
|
||||
/// This class inherits from the Rust `FirefoxAccount` and adds:
|
||||
/// - Automatic state persistence through `PersistCallback`.
|
||||
/// - Auth error signaling through observer notifications.
|
||||
/// - Some convenience higher-level datatypes, such as URLs rather than plain Strings.
|
||||
///
|
||||
/// Eventually we'd like to move all of this into the underlying Rust code, once UniFFI
|
||||
/// grows support for these extra features:
|
||||
/// - Callback interfaces in Swift: https://github.com/mozilla/uniffi-rs/issues/353
|
||||
/// - Higher-level data types: https://github.com/mozilla/uniffi-rs/issues/348
|
||||
///
|
||||
/// It's not yet clear how we might integrate with observer notifications in
|
||||
/// a cross-platform way, though.
|
||||
///
|
||||
class PersistedFirefoxAccount {
|
||||
private var persistCallback: PersistCallback?
|
||||
private var inner: FirefoxAccount
|
||||
|
||||
init(inner: FirefoxAccount) {
|
||||
self.inner = inner
|
||||
}
|
||||
|
||||
public convenience init(config: FxAConfig) {
|
||||
self.init(inner: FirefoxAccount(contentUrl: config.contentUrl,
|
||||
clientId: config.clientId,
|
||||
redirectUri: config.redirectUri,
|
||||
tokenServerUrlOverride: config.tokenServerUrlOverride))
|
||||
}
|
||||
|
||||
/// Registers a persistance callback. The callback will get called every time
|
||||
/// the `FxAccounts` state needs to be saved. The callback must
|
||||
/// persist the passed string in a secure location (like the keychain).
|
||||
public func registerPersistCallback(_ cb: PersistCallback) {
|
||||
persistCallback = cb
|
||||
}
|
||||
|
||||
/// Unregisters a persistance callback.
|
||||
public func unregisterPersistCallback() {
|
||||
persistCallback = nil
|
||||
}
|
||||
|
||||
public static func fromJSON(data: String) throws -> PersistedFirefoxAccount {
|
||||
return PersistedFirefoxAccount(inner: try FirefoxAccount.fromJson(data: data))
|
||||
}
|
||||
|
||||
public func toJSON() throws -> String {
|
||||
try inner.toJson()
|
||||
}
|
||||
|
||||
public func beginOAuthFlow(
|
||||
scopes: [String],
|
||||
entrypoint: String,
|
||||
metrics: MetricsParams = MetricsParams(parameters: [:])
|
||||
) throws -> URL {
|
||||
return try notifyAuthErrors {
|
||||
URL(string: try self.inner.beginOauthFlow(
|
||||
scopes: scopes,
|
||||
entrypoint: entrypoint,
|
||||
metrics: metrics
|
||||
))!
|
||||
}
|
||||
}
|
||||
|
||||
public func getPairingAuthorityURL() throws -> URL {
|
||||
return URL(string: try inner.getPairingAuthorityUrl())!
|
||||
}
|
||||
|
||||
public func beginPairingFlow(
|
||||
pairingUrl: String,
|
||||
scopes: [String],
|
||||
entrypoint: String,
|
||||
metrics: MetricsParams = MetricsParams(parameters: [:])
|
||||
) throws -> URL {
|
||||
return try notifyAuthErrors {
|
||||
URL(string: try self.inner.beginPairingFlow(pairingUrl: pairingUrl,
|
||||
scopes: scopes,
|
||||
entrypoint: entrypoint,
|
||||
metrics: metrics))!
|
||||
}
|
||||
}
|
||||
|
||||
public func completeOAuthFlow(code: String, state: String) throws {
|
||||
defer { tryPersistState() }
|
||||
try notifyAuthErrors {
|
||||
try self.inner.completeOauthFlow(code: code, state: state)
|
||||
}
|
||||
}
|
||||
|
||||
public func checkAuthorizationStatus() throws -> AuthorizationInfo {
|
||||
defer { tryPersistState() }
|
||||
return try notifyAuthErrors {
|
||||
try self.inner.checkAuthorizationStatus()
|
||||
}
|
||||
}
|
||||
|
||||
public func disconnect() {
|
||||
defer { tryPersistState() }
|
||||
inner.disconnect()
|
||||
}
|
||||
|
||||
public func getProfile(ignoreCache: Bool) throws -> Profile {
|
||||
defer { tryPersistState() }
|
||||
return try notifyAuthErrors {
|
||||
try self.inner.getProfile(ignoreCache: ignoreCache)
|
||||
}
|
||||
}
|
||||
|
||||
public func initializeDevice(
|
||||
name: String,
|
||||
deviceType: DeviceType,
|
||||
supportedCapabilities: [DeviceCapability]
|
||||
) throws {
|
||||
defer { tryPersistState() }
|
||||
try notifyAuthErrors {
|
||||
try self.inner.initializeDevice(name: name,
|
||||
deviceType: deviceType,
|
||||
supportedCapabilities: supportedCapabilities)
|
||||
}
|
||||
}
|
||||
|
||||
public func getCurrentDeviceId() throws -> String {
|
||||
return try notifyAuthErrors {
|
||||
try self.inner.getCurrentDeviceId()
|
||||
}
|
||||
}
|
||||
|
||||
public func getDevices(ignoreCache: Bool = false) throws -> [Device] {
|
||||
return try notifyAuthErrors {
|
||||
try self.inner.getDevices(ignoreCache: ignoreCache)
|
||||
}
|
||||
}
|
||||
|
||||
public func getAttachedClients() throws -> [AttachedClient] {
|
||||
return try notifyAuthErrors {
|
||||
try self.inner.getAttachedClients()
|
||||
}
|
||||
}
|
||||
|
||||
public func setDeviceName(_ name: String) throws {
|
||||
defer { tryPersistState() }
|
||||
try notifyAuthErrors {
|
||||
try self.inner.setDeviceName(displayName: name)
|
||||
}
|
||||
}
|
||||
|
||||
public func clearDeviceName() throws {
|
||||
defer { tryPersistState() }
|
||||
try notifyAuthErrors {
|
||||
try self.inner.clearDeviceName()
|
||||
}
|
||||
}
|
||||
|
||||
public func ensureCapabilities(supportedCapabilities: [DeviceCapability]) throws {
|
||||
defer { tryPersistState() }
|
||||
try notifyAuthErrors {
|
||||
try self.inner.ensureCapabilities(supportedCapabilities: supportedCapabilities)
|
||||
}
|
||||
}
|
||||
|
||||
public func setDevicePushSubscription(sub: DevicePushSubscription) throws {
|
||||
try notifyAuthErrors {
|
||||
try self.inner.setPushSubscription(subscription: sub)
|
||||
}
|
||||
}
|
||||
|
||||
public func handlePushMessage(payload: String) throws -> [AccountEvent] {
|
||||
defer { tryPersistState() }
|
||||
return try notifyAuthErrors {
|
||||
try self.inner.handlePushMessage(payload: payload)
|
||||
}
|
||||
}
|
||||
|
||||
public func pollDeviceCommands() throws -> [IncomingDeviceCommand] {
|
||||
defer { tryPersistState() }
|
||||
return try notifyAuthErrors {
|
||||
try self.inner.pollDeviceCommands()
|
||||
}
|
||||
}
|
||||
|
||||
public func sendSingleTab(targetDeviceId: String, title: String, url: String) throws {
|
||||
return try notifyAuthErrors {
|
||||
try self.inner.sendSingleTab(targetDeviceId: targetDeviceId, title: title, url: url)
|
||||
}
|
||||
}
|
||||
|
||||
public func getTokenServerEndpointURL() throws -> URL {
|
||||
return URL(string: try inner.getTokenServerEndpointUrl())!
|
||||
}
|
||||
|
||||
public func getConnectionSuccessURL() throws -> URL {
|
||||
return URL(string: try inner.getConnectionSuccessUrl())!
|
||||
}
|
||||
|
||||
public func getManageAccountURL(entrypoint: String) throws -> URL {
|
||||
return URL(string: try inner.getManageAccountUrl(entrypoint: entrypoint))!
|
||||
}
|
||||
|
||||
public func getManageDevicesURL(entrypoint: String) throws -> URL {
|
||||
return URL(string: try inner.getManageDevicesUrl(entrypoint: entrypoint))!
|
||||
}
|
||||
|
||||
public func getAccessToken(scope: String, ttl: UInt64? = nil) throws -> AccessTokenInfo {
|
||||
defer { tryPersistState() }
|
||||
return try notifyAuthErrors {
|
||||
try self.inner.getAccessToken(scope: scope, ttl: ttl == nil ? nil : Int64(clamping: ttl!))
|
||||
}
|
||||
}
|
||||
|
||||
public func getSessionToken() throws -> String {
|
||||
defer { tryPersistState() }
|
||||
return try notifyAuthErrors {
|
||||
try self.inner.getSessionToken()
|
||||
}
|
||||
}
|
||||
|
||||
public func handleSessionTokenChange(sessionToken: String) throws {
|
||||
defer { tryPersistState() }
|
||||
return try notifyAuthErrors {
|
||||
try self.inner.handleSessionTokenChange(sessionToken: sessionToken)
|
||||
}
|
||||
}
|
||||
|
||||
public func authorizeCodeUsingSessionToken(params: AuthorizationParameters) throws -> String {
|
||||
defer { tryPersistState() }
|
||||
return try notifyAuthErrors {
|
||||
try self.inner.authorizeCodeUsingSessionToken(params: params)
|
||||
}
|
||||
}
|
||||
|
||||
public func clearAccessTokenCache() {
|
||||
defer { tryPersistState() }
|
||||
inner.clearAccessTokenCache()
|
||||
}
|
||||
|
||||
public func gatherTelemetry() throws -> String {
|
||||
return try notifyAuthErrors {
|
||||
try self.inner.gatherTelemetry()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: not sure why we switched to returning a bool for the Swift wrapper here,
|
||||
// we should review and see if we can make it consistent with Rust and Kotlin.
|
||||
|
||||
public func migrateFromSessionToken(
|
||||
sessionToken: String,
|
||||
kSync: String,
|
||||
kXCS: String,
|
||||
copySessionToken: Bool
|
||||
) -> Bool {
|
||||
defer { tryPersistState() }
|
||||
do {
|
||||
_ = try inner.migrateFromSessionToken(sessionToken: sessionToken,
|
||||
kSync: kSync,
|
||||
kXcs: kXCS,
|
||||
copySessionToken: copySessionToken)
|
||||
return true
|
||||
} catch {
|
||||
FxALog.error("migrateFromSessionToken error: \(error).")
|
||||
reportAccountMigrationError(error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public func retryMigrateFromSessionToken() -> Bool {
|
||||
defer { tryPersistState() }
|
||||
do {
|
||||
_ = try inner.retryMigrateFromSessionToken()
|
||||
return true
|
||||
} catch {
|
||||
FxALog.error("retryMigrateFromSessionToken error: \(error).")
|
||||
reportAccountMigrationError(error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
internal func reportAccountMigrationError(_ error: Error) {
|
||||
// Not in migration state after throwing during migration == unrecoverable error.
|
||||
if isInMigrationState() {
|
||||
DispatchQueue.main.async {
|
||||
NotificationCenter.default.post(
|
||||
name: .accountMigrationFailed,
|
||||
object: nil,
|
||||
userInfo: ["error": error]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func isInMigrationState() -> Bool {
|
||||
return inner.isInMigrationState() != .none
|
||||
}
|
||||
|
||||
private func tryPersistState() {
|
||||
guard let cb = persistCallback else {
|
||||
return
|
||||
}
|
||||
do {
|
||||
let json = try toJSON()
|
||||
cb.persist(json: json)
|
||||
} catch {
|
||||
// Ignore the error because the prior operation might have worked,
|
||||
// but still log it.
|
||||
FxALog.error("FxAccounts internal state serialization failed.")
|
||||
}
|
||||
}
|
||||
|
||||
internal func notifyAuthErrors<T>(_ cb: () throws -> T) rethrows -> T {
|
||||
do {
|
||||
return try cb()
|
||||
} catch let error as FxaError {
|
||||
if case let .Authentication(msg) = error {
|
||||
FxALog.debug("Auth error caught: \(msg)")
|
||||
notifyAuthError()
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
internal func notifyAuthError() {
|
||||
NotificationCenter.default.post(name: .accountAuthException, object: nil)
|
||||
}
|
||||
}
|
||||
|
||||
public protocol PersistCallback {
|
||||
func persist(json: String)
|
||||
}
|
|
@ -1,170 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#pragma once
|
||||
#include <Foundation/NSObjCRuntime.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/*
|
||||
* This file contains headers for all of the structs and functions that map
|
||||
* directly to the functions defined in fxa-client/src/ffi.rs,
|
||||
* fxa-client/ffi/src/lib.rs, and components/support/ffi/src/error.rs.
|
||||
*
|
||||
* The C in this file is specifically formatted to be used with Objective C and
|
||||
* Swift and contains macros and flags that will not be recognised by other C
|
||||
* based languages.
|
||||
*/
|
||||
|
||||
/*
|
||||
Error codes reported by the fxa-client library, from fxa-client/src/ffi.rs
|
||||
*/
|
||||
typedef enum FxAErrorCode {
|
||||
FxA_InternalPanic = -1,
|
||||
FxA_NoError = 0,
|
||||
FxA_Other = 1,
|
||||
FxA_AuthenticationError = 2,
|
||||
FxA_NetworkError = 3,
|
||||
} FxAErrorCode;
|
||||
|
||||
/*
|
||||
A mapping of the ExternError repr(C) Rust struct, from
|
||||
components/support/ffi/src/error.rs.
|
||||
*/
|
||||
typedef struct FxAError {
|
||||
FxAErrorCode code;
|
||||
char *_Nullable message;
|
||||
} FxAError;
|
||||
|
||||
/*
|
||||
A mapping of the ByteBuffer repr(C) Rust struct, from
|
||||
components/support/ffi/src/lib.rs.
|
||||
*/
|
||||
typedef struct FxARustBuffer {
|
||||
int64_t len;
|
||||
uint8_t *_Nullable data;
|
||||
} FxARustBuffer;
|
||||
|
||||
typedef uint64_t FirefoxAccountHandle;
|
||||
|
||||
char *_Nullable fxa_begin_oauth_flow(FirefoxAccountHandle handle,
|
||||
const char *_Nonnull scopes,
|
||||
const char *_Nonnull entrypoint,
|
||||
uint8_t const *_Nonnull metrics_params,
|
||||
int32_t metrics_params_len,
|
||||
FxAError *_Nonnull out);
|
||||
|
||||
char *_Nullable fxa_begin_pairing_flow(FirefoxAccountHandle handle,
|
||||
const char *_Nonnull pairing_url,
|
||||
const char *_Nonnull scopes,
|
||||
const char *_Nonnull entrypoint,
|
||||
uint8_t const *_Nonnull metrics_params,
|
||||
int32_t metrics_params_len,
|
||||
FxAError *_Nonnull out);
|
||||
|
||||
void fxa_complete_oauth_flow(FirefoxAccountHandle handle,
|
||||
const char *_Nonnull code,
|
||||
const char *_Nonnull state,
|
||||
FxAError *_Nonnull out);
|
||||
|
||||
FxARustBuffer fxa_get_access_token(FirefoxAccountHandle handle,
|
||||
const char *_Nonnull scope, uint64_t ttl,
|
||||
FxAError *_Nonnull out);
|
||||
|
||||
char *_Nullable fxa_get_session_token(FirefoxAccountHandle handle,
|
||||
FxAError *_Nonnull out);
|
||||
|
||||
void fxa_clear_access_token_cache(FirefoxAccountHandle handle,
|
||||
FxAError *_Nonnull out);
|
||||
|
||||
void fxa_handle_session_token_change(FirefoxAccountHandle handle,
|
||||
const char *_Nonnull new_session_token,
|
||||
FxAError *_Nonnull out);
|
||||
|
||||
void fxa_disconnect(FirefoxAccountHandle handle, FxAError *_Nonnull out);
|
||||
|
||||
FxARustBuffer fxa_check_authorization_status(FirefoxAccountHandle handle,
|
||||
FxAError *_Nonnull out);
|
||||
|
||||
FirefoxAccountHandle fxa_from_json(const char *_Nonnull json,
|
||||
FxAError *_Nonnull out);
|
||||
|
||||
char *_Nullable fxa_to_json(FirefoxAccountHandle handle,
|
||||
FxAError *_Nonnull out);
|
||||
|
||||
FirefoxAccountHandle fxa_new(const char *_Nonnull content_base,
|
||||
const char *_Nonnull client_id,
|
||||
const char *_Nonnull redirect_uri,
|
||||
const char *_Nullable token_server_url_override,
|
||||
FxAError *_Nonnull out);
|
||||
|
||||
FxARustBuffer fxa_profile(FirefoxAccountHandle handle, uint8_t ignore_cache,
|
||||
FxAError *_Nonnull out);
|
||||
|
||||
FxARustBuffer fxa_get_devices(FirefoxAccountHandle handle, uint8_t ignore_cache,
|
||||
FxAError *_Nonnull out);
|
||||
|
||||
FxARustBuffer fxa_poll_device_commands(FirefoxAccountHandle handle,
|
||||
FxAError *_Nonnull out);
|
||||
|
||||
FxARustBuffer fxa_handle_push_message(FirefoxAccountHandle handle,
|
||||
const char *_Nonnull payload,
|
||||
FxAError *_Nonnull out);
|
||||
|
||||
void fxa_send_tab(FirefoxAccountHandle handle, const char *_Nonnull targetId,
|
||||
const char *_Nonnull title, const char *_Nonnull url,
|
||||
FxAError *_Nonnull out);
|
||||
|
||||
void fxa_set_device_name(FirefoxAccountHandle handle,
|
||||
const char *_Nonnull displayName,
|
||||
FxAError *_Nonnull out);
|
||||
|
||||
void fxa_set_push_subscription(FirefoxAccountHandle handle,
|
||||
const char *_Nonnull endpoint,
|
||||
const char *_Nonnull publicKey,
|
||||
const char *_Nonnull authKey,
|
||||
FxAError *_Nonnull out);
|
||||
|
||||
void fxa_initialize_device(FirefoxAccountHandle handle,
|
||||
const char *_Nonnull name, int32_t device_type,
|
||||
uint8_t const *_Nonnull capabilities_ptr,
|
||||
int32_t capabilities_len, FxAError *_Nonnull out);
|
||||
|
||||
void fxa_ensure_capabilities(FirefoxAccountHandle handle,
|
||||
uint8_t const *_Nonnull capabilities_ptr,
|
||||
int32_t capabilities_len, FxAError *_Nonnull out);
|
||||
|
||||
char *_Nullable fxa_migrate_from_session_token(
|
||||
FirefoxAccountHandle handle, const char *_Nonnull sessionToken,
|
||||
const char *_Nonnull kSync, const char *_Nonnull kXCS,
|
||||
uint8_t copySessionToken, FxAError *_Nonnull out);
|
||||
|
||||
char *_Nullable fxa_retry_migrate_from_session_token(
|
||||
FirefoxAccountHandle handle, FxAError *_Nonnull out);
|
||||
|
||||
uint8_t fxa_is_in_migration_state(FirefoxAccountHandle handle,
|
||||
FxAError *_Nonnull out);
|
||||
|
||||
char *_Nullable fxa_get_token_server_endpoint_url(FirefoxAccountHandle handle,
|
||||
FxAError *_Nonnull out);
|
||||
|
||||
char *_Nullable fxa_get_pairing_authority_url(FirefoxAccountHandle handle,
|
||||
FxAError *_Nonnull out);
|
||||
|
||||
char *_Nullable fxa_get_connection_success_url(FirefoxAccountHandle handle,
|
||||
FxAError *_Nonnull out);
|
||||
|
||||
char *_Nullable fxa_get_manage_account_url(FirefoxAccountHandle handle,
|
||||
const char *_Nonnull entrypoint,
|
||||
FxAError *_Nonnull out);
|
||||
|
||||
char *_Nullable fxa_get_manage_devices_url(FirefoxAccountHandle handle,
|
||||
const char *_Nonnull entrypoint,
|
||||
FxAError *_Nonnull out);
|
||||
|
||||
char *_Nullable fxa_gather_telemetry(FirefoxAccountHandle handle,
|
||||
FxAError *_Nonnull out);
|
||||
|
||||
void fxa_str_free(char *_Nullable ptr);
|
||||
void fxa_free(FirefoxAccountHandle h, FxAError *_Nonnull out);
|
||||
void fxa_bytebuffer_free(FxARustBuffer buffer);
|
|
@ -1,371 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import Foundation
|
||||
import SwiftProtobuf
|
||||
|
||||
/// This class wraps Rust calls safely and performs necessary type conversions.
|
||||
open class RustFxAccount {
|
||||
private let raw: UInt64
|
||||
|
||||
internal init(raw: UInt64) {
|
||||
self.raw = raw
|
||||
}
|
||||
|
||||
/// Create a `RustFxAccount` from scratch. This is suitable for callers using the
|
||||
/// OAuth Flow.
|
||||
public required convenience init(config: FxAConfig) throws {
|
||||
let pointer = try rustCall { err in
|
||||
fxa_new(config.contentUrl, config.clientId, config.redirectUri, config.tokenServerUrlOverride, err)
|
||||
}
|
||||
self.init(raw: pointer)
|
||||
}
|
||||
|
||||
/// Restore a previous instance of `RustFxAccount` from a serialized state (obtained with `toJSON(...)`).
|
||||
public required convenience init(fromJsonState state: String) throws {
|
||||
let pointer = try rustCall { err in fxa_from_json(state, err) }
|
||||
self.init(raw: pointer)
|
||||
}
|
||||
|
||||
deinit {
|
||||
if self.raw != 0 {
|
||||
try! rustCall { err in
|
||||
// Is `try!` the right thing to do? We should only hit an error here
|
||||
// for panics and handle misuse, both inidicate bugs in our code
|
||||
// (the first in the rust code, the 2nd in this swift wrapper).
|
||||
fxa_free(self.raw, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Serializes the state of a `RustFxAccount` instance. It can be restored
|
||||
/// later with `fromJSON(...)`. It is the responsability of the caller to
|
||||
/// persist that serialized state regularly (after operations that mutate
|
||||
/// `RustFxAccount`) in a **secure** location.
|
||||
open func toJSON() throws -> String {
|
||||
let ptr = try rustCall { err in fxa_to_json(self.raw, err) }
|
||||
return String(freeingFxaString: ptr)
|
||||
}
|
||||
|
||||
/// Gets the logged-in user profile.
|
||||
/// Throws `FirefoxAccountError.Unauthorized` if we couldn't find any suitable access token
|
||||
/// to make that call. The caller should then start the OAuth Flow again with
|
||||
/// the "profile" scope.
|
||||
open func getProfile(ignoreCache: Bool) throws -> Profile {
|
||||
let ignoreCacheArg = UInt8(ignoreCache ? 1 : 0)
|
||||
let ptr = try rustCall { err in
|
||||
fxa_profile(self.raw, ignoreCacheArg, err)
|
||||
}
|
||||
defer { fxa_bytebuffer_free(ptr) }
|
||||
let msg = try! MsgTypes_Profile(serializedData: Data(rustBuffer: ptr))
|
||||
return Profile(msg: msg)
|
||||
}
|
||||
|
||||
open func getTokenServerEndpointURL() throws -> URL {
|
||||
let ptr = try rustCall { err in
|
||||
fxa_get_token_server_endpoint_url(self.raw, err)
|
||||
}
|
||||
return URL(string: String(freeingFxaString: ptr))!
|
||||
}
|
||||
|
||||
open func getPairingAuthorityURL() throws -> URL {
|
||||
let ptr = try rustCall { err in
|
||||
fxa_get_pairing_authority_url(self.raw, err)
|
||||
}
|
||||
return URL(string: String(freeingFxaString: ptr))!
|
||||
}
|
||||
|
||||
open func getConnectionSuccessURL() throws -> URL {
|
||||
let ptr = try rustCall { err in
|
||||
fxa_get_connection_success_url(self.raw, err)
|
||||
}
|
||||
return URL(string: String(freeingFxaString: ptr))!
|
||||
}
|
||||
|
||||
open func getManageAccountURL(entrypoint: String) throws -> URL {
|
||||
let ptr = try rustCall { err in
|
||||
fxa_get_manage_account_url(self.raw, entrypoint, err)
|
||||
}
|
||||
return URL(string: String(freeingFxaString: ptr))!
|
||||
}
|
||||
|
||||
open func getManageDevicesURL(entrypoint: String) throws -> URL {
|
||||
let ptr = try rustCall { err in
|
||||
fxa_get_manage_devices_url(self.raw, entrypoint, err)
|
||||
}
|
||||
return URL(string: String(freeingFxaString: ptr))!
|
||||
}
|
||||
|
||||
/// Request a OAuth token by starting a new OAuth flow.
|
||||
///
|
||||
/// This function returns a URL string that the caller should open in a webview.
|
||||
///
|
||||
/// Once the user has confirmed the authorization grant, they will get redirected to `redirect_url`:
|
||||
/// the caller must intercept that redirection, extract the `code` and `state` query parameters and call
|
||||
/// `completeOAuthFlow(...)` to complete the flow.
|
||||
open func beginOAuthFlow(
|
||||
scopes: [String],
|
||||
entrypoint: String,
|
||||
metricsParams: MetricsParams
|
||||
) throws -> URL {
|
||||
let scope = scopes.joined(separator: " ")
|
||||
let (metricsParamsData, metricsParamsLen) = msgToBuffer(msg: metricsParams.toMsg())
|
||||
let ptr = try metricsParamsData.withUnsafeBytes { bytes in
|
||||
try rustCall { err in
|
||||
fxa_begin_oauth_flow(
|
||||
self.raw,
|
||||
scope,
|
||||
entrypoint,
|
||||
bytes.bindMemory(to: UInt8.self).baseAddress!,
|
||||
metricsParamsLen,
|
||||
err
|
||||
)
|
||||
}
|
||||
}
|
||||
return URL(string: String(freeingFxaString: ptr))!
|
||||
}
|
||||
|
||||
open func beginPairingFlow(
|
||||
pairingUrl: String,
|
||||
scopes: [String],
|
||||
entrypoint: String,
|
||||
metricsParams: MetricsParams
|
||||
) throws -> URL {
|
||||
let scope = scopes.joined(separator: " ")
|
||||
let (metricsParamsData, metricsParamsLen) = msgToBuffer(msg: metricsParams.toMsg())
|
||||
let ptr = try metricsParamsData.withUnsafeBytes { bytes in
|
||||
try rustCall { err in
|
||||
fxa_begin_pairing_flow(
|
||||
self.raw,
|
||||
pairingUrl,
|
||||
scope,
|
||||
entrypoint,
|
||||
bytes.bindMemory(to: UInt8.self).baseAddress!,
|
||||
metricsParamsLen,
|
||||
err
|
||||
)
|
||||
}
|
||||
}
|
||||
return URL(string: String(freeingFxaString: ptr))!
|
||||
}
|
||||
|
||||
/// Finish an OAuth flow initiated by `beginOAuthFlow(...)` and returns token/keys.
|
||||
///
|
||||
/// This resulting token might not have all the `scopes` the caller have requested (e.g. the user
|
||||
/// might have denied some of them): it is the responsibility of the caller to accomodate that.
|
||||
open func completeOAuthFlow(code: String, state: String) throws {
|
||||
try rustCall { err in
|
||||
fxa_complete_oauth_flow(self.raw, code, state, err)
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to get an OAuth access token.
|
||||
///
|
||||
/// `ttl` corresponds to the time in seconds for which the token will be used.
|
||||
/// Throws `FirefoxAccountError.Unauthorized` if we couldn't provide an access token
|
||||
/// for this scope. The caller should then start the OAuth Flow again with
|
||||
/// the desired scope.
|
||||
open func getAccessToken(scope: String, ttl: UInt64? = nil) throws -> AccessTokenInfo {
|
||||
let ptr = try rustCall { err in
|
||||
// A zero ttl here means to use the server-controlled default.
|
||||
fxa_get_access_token(self.raw, scope, ttl ?? .zero, err)
|
||||
}
|
||||
defer { fxa_bytebuffer_free(ptr) }
|
||||
let msg = try! MsgTypes_AccessTokenInfo(serializedData: Data(rustBuffer: ptr))
|
||||
return AccessTokenInfo(msg: msg)
|
||||
}
|
||||
|
||||
/// Get the session token. If non-present an error will be thrown.
|
||||
open func getSessionToken() throws -> String {
|
||||
let ptr = try rustCall { err in
|
||||
fxa_get_session_token(self.raw, err)
|
||||
}
|
||||
return String(freeingFxaString: ptr)
|
||||
}
|
||||
|
||||
/// Check whether the refreshToken is active
|
||||
open func checkAuthorizationStatus() throws -> IntrospectInfo {
|
||||
let ptr = try rustCall { err in
|
||||
fxa_check_authorization_status(self.raw, err)
|
||||
}
|
||||
defer { fxa_bytebuffer_free(ptr) }
|
||||
let msg = try! MsgTypes_IntrospectInfo(serializedData: Data(rustBuffer: ptr))
|
||||
return IntrospectInfo(msg: msg)
|
||||
}
|
||||
|
||||
/// This method should be called when a request made with
|
||||
/// an OAuth token failed with an authentication error.
|
||||
/// It clears the internal cache of OAuth access tokens,
|
||||
/// so the caller can try to call `getAccessToken` or `getProfile`
|
||||
/// again.
|
||||
open func clearAccessTokenCache() throws {
|
||||
try rustCall { err in
|
||||
fxa_clear_access_token_cache(self.raw, err)
|
||||
}
|
||||
}
|
||||
|
||||
/// Disconnect from the account and optionaly destroy our device record.
|
||||
/// `beginOAuthFlow(...)` will need to be called to reconnect.
|
||||
open func disconnect() throws {
|
||||
try rustCall { err in
|
||||
fxa_disconnect(self.raw, err)
|
||||
}
|
||||
}
|
||||
|
||||
open func getDevices(ignoreCache: Bool = false) throws -> [Device] {
|
||||
let ignoreCacheArg = UInt8(ignoreCache ? 1 : 0)
|
||||
let ptr = try rustCall { err in
|
||||
fxa_get_devices(self.raw, ignoreCacheArg, err)
|
||||
}
|
||||
defer { fxa_bytebuffer_free(ptr) }
|
||||
let msg = try! MsgTypes_Devices(serializedData: Data(rustBuffer: ptr))
|
||||
return Device.fromCollectionMsg(msg: msg)
|
||||
}
|
||||
|
||||
open func setDeviceDisplayName(_ name: String) throws {
|
||||
try rustCall { err in
|
||||
fxa_set_device_name(self.raw, name, err)
|
||||
}
|
||||
}
|
||||
|
||||
open func pollDeviceCommands() throws -> [IncomingDeviceCommand] {
|
||||
let ptr = try rustCall { err in
|
||||
fxa_poll_device_commands(self.raw, err)
|
||||
}
|
||||
defer { fxa_bytebuffer_free(ptr) }
|
||||
let msg = try! MsgTypes_IncomingDeviceCommands(serializedData: Data(rustBuffer: ptr))
|
||||
return IncomingDeviceCommand.fromCollectionMsg(msg: msg)
|
||||
}
|
||||
|
||||
open func handlePushMessage(payload: String) throws -> [AccountEvent] {
|
||||
let ptr = try rustCall { err in
|
||||
fxa_handle_push_message(self.raw, payload, err)
|
||||
}
|
||||
defer { fxa_bytebuffer_free(ptr) }
|
||||
let msg = try! MsgTypes_AccountEvents(serializedData: Data(rustBuffer: ptr))
|
||||
return AccountEvent.fromCollectionMsg(msg: msg)
|
||||
}
|
||||
|
||||
open func sendSingleTab(targetId: String, title: String, url: String) throws {
|
||||
try rustCall { err in
|
||||
fxa_send_tab(self.raw, targetId, title, url, err)
|
||||
}
|
||||
}
|
||||
|
||||
open func setDevicePushSubscription(endpoint: String, publicKey: String, authKey: String) throws {
|
||||
try rustCall { err in
|
||||
fxa_set_push_subscription(self.raw, endpoint, publicKey, authKey, err)
|
||||
}
|
||||
}
|
||||
|
||||
open func initializeDevice(
|
||||
name: String,
|
||||
deviceType: DeviceType,
|
||||
supportedCapabilities: [DeviceCapability]
|
||||
) throws {
|
||||
let (data, size) = msgToBuffer(msg: supportedCapabilities.toCollectionMsg())
|
||||
try data.withUnsafeBytes { bytes in
|
||||
try rustCall { err in
|
||||
fxa_initialize_device(
|
||||
self.raw,
|
||||
name,
|
||||
Int32(deviceType.toMsg().rawValue),
|
||||
bytes.bindMemory(to: UInt8.self).baseAddress!,
|
||||
size,
|
||||
err
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open func ensureCapabilities(supportedCapabilities: [DeviceCapability]) throws {
|
||||
let (data, size) = msgToBuffer(msg: supportedCapabilities.toCollectionMsg())
|
||||
try data.withUnsafeBytes { bytes in
|
||||
try rustCall { err in
|
||||
fxa_ensure_capabilities(
|
||||
self.raw,
|
||||
bytes.bindMemory(to: UInt8.self).baseAddress!,
|
||||
size,
|
||||
err
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open func migrateFromSessionToken(sessionToken: String, kSync: String, kXCS: String) throws -> Bool {
|
||||
let json = try nullableRustCall { err in
|
||||
fxa_migrate_from_session_token(self.raw, sessionToken, kSync, kXCS, 0 /* reuse session token */, err)
|
||||
}
|
||||
|
||||
defer {
|
||||
if let json = json {
|
||||
fxa_str_free(json)
|
||||
}
|
||||
}
|
||||
// We don't parse the JSON coz nobody uses it...
|
||||
return json != nil
|
||||
}
|
||||
|
||||
open func retryMigrateFromSessionToken() throws -> Bool {
|
||||
let json = try nullableRustCall { err in
|
||||
fxa_retry_migrate_from_session_token(self.raw, err)
|
||||
}
|
||||
|
||||
defer {
|
||||
if let json = json {
|
||||
fxa_str_free(json)
|
||||
}
|
||||
}
|
||||
return json != nil
|
||||
}
|
||||
|
||||
open func isInMigrationState() throws -> Bool {
|
||||
let number = try rustCall { err in
|
||||
fxa_is_in_migration_state(self.raw, err)
|
||||
}
|
||||
let state = MigrationState.fromNumber(number)
|
||||
// We never initiate a "copy-session-token" migration,
|
||||
// so we can just return a boolean.
|
||||
return state == .reuseSessionToken
|
||||
}
|
||||
|
||||
open func handleSessionTokenChange(sessionToken: String) throws {
|
||||
try rustCall { err in
|
||||
fxa_handle_session_token_change(self.raw, sessionToken, err)
|
||||
}
|
||||
}
|
||||
|
||||
open func gatherTelemetry() throws -> String? {
|
||||
let maybeEvents = try nullableRustCall { err in fxa_gather_telemetry(self.raw, err) }
|
||||
guard let events = maybeEvents else {
|
||||
return nil
|
||||
}
|
||||
return String(freeingFxaString: events)
|
||||
}
|
||||
|
||||
private func msgToBuffer(msg: SwiftProtobuf.Message) -> (Data, Int32) {
|
||||
let data = try! msg.serializedData()
|
||||
let size = Int32(data.count)
|
||||
return (data, size)
|
||||
}
|
||||
}
|
||||
|
||||
// This queue serves as a semaphore to the rust layer.
|
||||
private let fxaRustQueue = DispatchQueue(label: "com.mozilla.fxa-rust")
|
||||
|
||||
internal func rustCall<T>(_ cb: (UnsafeMutablePointer<FxAError>) throws -> T?) throws -> T {
|
||||
return try FirefoxAccountError.unwrap { err in
|
||||
try fxaRustQueue.sync {
|
||||
try cb(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal func nullableRustCall<T>(_ cb: (UnsafeMutablePointer<FxAError>) throws -> T?) throws -> T? {
|
||||
return try FirefoxAccountError.tryUnwrap { err in
|
||||
try fxaRustQueue.sync {
|
||||
try cb(err)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,345 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
//! This module implement the traits and some types that make the FFI code easier to manage.
|
||||
//!
|
||||
//! Note that the FxA FFI is older than the other FFIs in application-services, and has (direct,
|
||||
//! low-level) bindings that live in the mozilla-mobile/android-components repo. As a result, it's a
|
||||
//! bit harder to change (anything breaking the ABI requires careful synchronization of updates
|
||||
//! across two repos), and doesn't follow all the same conventions that are followed by the other
|
||||
//! FFIs.
|
||||
//!
|
||||
//! None of this is that bad in practice, but much of it is not ideal.
|
||||
|
||||
pub use crate::oauth::{AuthorizationPKCEParams, AuthorizationParameters, MetricsParams};
|
||||
use crate::{
|
||||
commands,
|
||||
device::{Capability as DeviceCapability, Device, PushSubscription, Type as DeviceType},
|
||||
msg_types, send_tab, AccessTokenInfo, AccountEvent, Error, ErrorKind, IncomingDeviceCommand,
|
||||
IntrospectInfo, Profile, Result, ScopedKey,
|
||||
};
|
||||
use ffi_support::{
|
||||
implement_into_ffi_by_delegation, implement_into_ffi_by_protobuf, ErrorCode, ExternError,
|
||||
};
|
||||
|
||||
pub mod error_codes {
|
||||
// Note: -1 and 0 (panic and success) codes are reserved by the ffi-support library
|
||||
|
||||
/// Catch-all error code used for anything that's not a panic or covered by AUTHENTICATION.
|
||||
pub const OTHER: i32 = 1;
|
||||
|
||||
/// Used by `ErrorKind::NoCachedTokens`, `ErrorKind::NoScopedKey`
|
||||
/// and `ErrorKind::RemoteError`'s where `code == 401`.
|
||||
pub const AUTHENTICATION: i32 = 2;
|
||||
|
||||
/// Code for network errors.
|
||||
pub const NETWORK: i32 = 3;
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// data is a raw pointer to the protobuf data
|
||||
/// get_buffer will return an error if the length is invalid,
|
||||
/// or if the pointer is a null pointer
|
||||
pub unsafe fn from_protobuf_ptr<T, F: prost::Message + Default + Into<T>>(
|
||||
data: *const u8,
|
||||
len: i32,
|
||||
) -> Result<T> {
|
||||
let buffer = get_buffer(data, len)?;
|
||||
let item: Result<F, _> = prost::Message::decode(buffer);
|
||||
item.map(|inner| inner.into()).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn get_code(err: &Error) -> ErrorCode {
|
||||
match err.kind() {
|
||||
ErrorKind::RemoteError { code: 401, .. }
|
||||
| ErrorKind::NoRefreshToken
|
||||
| ErrorKind::NoScopedKey(_)
|
||||
| ErrorKind::NoCachedToken(_) => {
|
||||
log::warn!("Authentication error: {:?}", err);
|
||||
ErrorCode::new(error_codes::AUTHENTICATION)
|
||||
}
|
||||
ErrorKind::RequestError(_) => {
|
||||
log::warn!("Network error: {:?}", err);
|
||||
ErrorCode::new(error_codes::NETWORK)
|
||||
}
|
||||
_ => {
|
||||
log::warn!("Unexpected error: {:?}", err);
|
||||
ErrorCode::new(error_codes::OTHER)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for ExternError {
|
||||
fn from(err: Error) -> ExternError {
|
||||
ExternError::new_error(get_code(&err), err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AccessTokenInfo> for msg_types::AccessTokenInfo {
|
||||
fn from(a: AccessTokenInfo) -> Self {
|
||||
msg_types::AccessTokenInfo {
|
||||
scope: a.scope,
|
||||
token: a.token,
|
||||
key: a.key.map(Into::into),
|
||||
expires_at: a.expires_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IntrospectInfo> for msg_types::IntrospectInfo {
|
||||
fn from(a: IntrospectInfo) -> Self {
|
||||
msg_types::IntrospectInfo { active: a.active }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ScopedKey> for msg_types::ScopedKey {
|
||||
fn from(sk: ScopedKey) -> Self {
|
||||
msg_types::ScopedKey {
|
||||
kty: sk.kty,
|
||||
scope: sk.scope,
|
||||
k: sk.k,
|
||||
kid: sk.kid,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Profile> for msg_types::Profile {
|
||||
fn from(p: Profile) -> Self {
|
||||
Self {
|
||||
avatar: Some(p.avatar),
|
||||
avatar_default: Some(p.avatar_default),
|
||||
display_name: p.display_name,
|
||||
email: Some(p.email),
|
||||
uid: Some(p.uid),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn command_to_capability(command: &str) -> Option<msg_types::device::Capability> {
|
||||
match command {
|
||||
commands::send_tab::COMMAND_NAME => Some(msg_types::device::Capability::SendTab),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Device> for msg_types::Device {
|
||||
fn from(d: Device) -> Self {
|
||||
let capabilities = d
|
||||
.available_commands
|
||||
.keys()
|
||||
.filter_map(|c| command_to_capability(c).map(|cc| cc as i32))
|
||||
.collect();
|
||||
Self {
|
||||
id: d.common.id,
|
||||
display_name: d.common.display_name,
|
||||
r#type: Into::<msg_types::device::Type>::into(d.common.device_type) as i32,
|
||||
push_subscription: d.common.push_subscription.map(Into::into),
|
||||
push_endpoint_expired: d.common.push_endpoint_expired,
|
||||
is_current_device: d.is_current_device,
|
||||
last_access_time: d.last_access_time,
|
||||
capabilities,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DeviceType> for msg_types::device::Type {
|
||||
fn from(t: DeviceType) -> Self {
|
||||
match t {
|
||||
DeviceType::Desktop => msg_types::device::Type::Desktop,
|
||||
DeviceType::Mobile => msg_types::device::Type::Mobile,
|
||||
DeviceType::Tablet => msg_types::device::Type::Tablet,
|
||||
DeviceType::VR => msg_types::device::Type::Vr,
|
||||
DeviceType::TV => msg_types::device::Type::Tv,
|
||||
DeviceType::Unknown => msg_types::device::Type::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<msg_types::device::Type> for DeviceType {
|
||||
fn from(t: msg_types::device::Type) -> Self {
|
||||
match t {
|
||||
msg_types::device::Type::Desktop => DeviceType::Desktop,
|
||||
msg_types::device::Type::Mobile => DeviceType::Mobile,
|
||||
msg_types::device::Type::Tablet => DeviceType::Tablet,
|
||||
msg_types::device::Type::Vr => DeviceType::VR,
|
||||
msg_types::device::Type::Tv => DeviceType::TV,
|
||||
msg_types::device::Type::Unknown => DeviceType::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PushSubscription> for msg_types::device::PushSubscription {
|
||||
fn from(p: PushSubscription) -> Self {
|
||||
Self {
|
||||
endpoint: p.endpoint,
|
||||
public_key: p.public_key,
|
||||
auth_key: p.auth_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AccountEvent> for msg_types::AccountEvent {
|
||||
fn from(e: AccountEvent) -> Self {
|
||||
match e {
|
||||
AccountEvent::IncomingDeviceCommand(command) => Self {
|
||||
r#type: msg_types::account_event::AccountEventType::IncomingDeviceCommand as i32,
|
||||
data: Some(msg_types::account_event::Data::DeviceCommand(
|
||||
(*command).into(),
|
||||
)),
|
||||
},
|
||||
AccountEvent::ProfileUpdated => Self {
|
||||
r#type: msg_types::account_event::AccountEventType::ProfileUpdated as i32,
|
||||
data: None,
|
||||
},
|
||||
AccountEvent::AccountAuthStateChanged => Self {
|
||||
r#type: msg_types::account_event::AccountEventType::AccountAuthStateChanged as i32,
|
||||
data: None,
|
||||
},
|
||||
AccountEvent::AccountDestroyed => Self {
|
||||
r#type: msg_types::account_event::AccountEventType::AccountDestroyed as i32,
|
||||
data: None,
|
||||
},
|
||||
AccountEvent::DeviceConnected { device_name } => Self {
|
||||
r#type: msg_types::account_event::AccountEventType::DeviceConnected as i32,
|
||||
data: Some(msg_types::account_event::Data::DeviceConnectedName(
|
||||
device_name,
|
||||
)),
|
||||
},
|
||||
AccountEvent::DeviceDisconnected {
|
||||
device_id,
|
||||
is_local_device,
|
||||
} => Self {
|
||||
r#type: msg_types::account_event::AccountEventType::DeviceDisconnected as i32,
|
||||
data: Some(msg_types::account_event::Data::DeviceDisconnectedData(
|
||||
msg_types::account_event::DeviceDisconnectedData {
|
||||
device_id,
|
||||
is_local_device,
|
||||
},
|
||||
)),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IncomingDeviceCommand> for msg_types::IncomingDeviceCommand {
|
||||
fn from(data: IncomingDeviceCommand) -> Self {
|
||||
match data {
|
||||
IncomingDeviceCommand::TabReceived { sender, payload } => Self {
|
||||
r#type: msg_types::incoming_device_command::IncomingDeviceCommandType::TabReceived
|
||||
as i32,
|
||||
data: Some(msg_types::incoming_device_command::Data::TabReceivedData(
|
||||
msg_types::incoming_device_command::SendTabData {
|
||||
from: sender.map(Into::into),
|
||||
entries: payload.entries.into_iter().map(Into::into).collect(),
|
||||
},
|
||||
)),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<send_tab::TabHistoryEntry>
|
||||
for msg_types::incoming_device_command::send_tab_data::TabHistoryEntry
|
||||
{
|
||||
fn from(data: send_tab::TabHistoryEntry) -> Self {
|
||||
Self {
|
||||
title: data.title,
|
||||
url: data.url,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<msg_types::device::Capability> for DeviceCapability {
|
||||
fn from(cap: msg_types::device::Capability) -> Self {
|
||||
match cap {
|
||||
msg_types::device::Capability::SendTab => DeviceCapability::SendTab,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DeviceCapability {
|
||||
/// # Safety
|
||||
/// Deref pointer thus unsafe
|
||||
pub unsafe fn from_protobuf_array_ptr(ptr: *const u8, len: i32) -> Result<Vec<Self>> {
|
||||
let buffer = get_buffer(ptr, len)?;
|
||||
let capabilities: Result<msg_types::Capabilities, _> = prost::Message::decode(buffer);
|
||||
Ok(capabilities
|
||||
.map(|cc| cc.to_capabilities_vec())
|
||||
.unwrap_or_else(|_| vec![]))
|
||||
}
|
||||
}
|
||||
|
||||
impl msg_types::Capabilities {
|
||||
pub fn to_capabilities_vec(&self) -> Vec<DeviceCapability> {
|
||||
self.capability
|
||||
.iter()
|
||||
.map(|c| msg_types::device::Capability::from_i32(*c).unwrap().into())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn get_buffer<'a>(data: *const u8, len: i32) -> Result<&'a [u8]> {
|
||||
match len {
|
||||
len if len < 0 => Err(ErrorKind::InvalidBufferLength(len).into()),
|
||||
0 => Ok(&[]),
|
||||
_ => {
|
||||
if data.is_null() {
|
||||
return Err(ErrorKind::NullPointer.into());
|
||||
}
|
||||
Ok(std::slice::from_raw_parts(data, len as usize))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<msg_types::AuthorizationParams> for AuthorizationParameters {
|
||||
fn from(proto_params: msg_types::AuthorizationParams) -> Self {
|
||||
Self {
|
||||
client_id: proto_params.client_id,
|
||||
scope: proto_params
|
||||
.scope
|
||||
.split_whitespace()
|
||||
.map(|s| s.to_string())
|
||||
.collect(),
|
||||
state: proto_params.state,
|
||||
access_type: proto_params.access_type,
|
||||
pkce_params: proto_params
|
||||
.pkce_params
|
||||
.map(|pkce_params| pkce_params.into()),
|
||||
keys_jwk: proto_params.keys_jwk,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<msg_types::MetricsParams> for MetricsParams {
|
||||
fn from(proto_metrics_params: msg_types::MetricsParams) -> Self {
|
||||
Self {
|
||||
parameters: proto_metrics_params.parameters,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<msg_types::AuthorizationPkceParams> for AuthorizationPKCEParams {
|
||||
fn from(proto_key_params: msg_types::AuthorizationPkceParams) -> Self {
|
||||
Self {
|
||||
code_challenge: proto_key_params.code_challenge,
|
||||
code_challenge_method: proto_key_params.code_challenge_method,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
implement_into_ffi_by_protobuf!(msg_types::Profile);
|
||||
implement_into_ffi_by_delegation!(Profile, msg_types::Profile);
|
||||
implement_into_ffi_by_protobuf!(msg_types::AccessTokenInfo);
|
||||
implement_into_ffi_by_delegation!(AccessTokenInfo, msg_types::AccessTokenInfo);
|
||||
implement_into_ffi_by_protobuf!(msg_types::IntrospectInfo);
|
||||
implement_into_ffi_by_delegation!(IntrospectInfo, msg_types::IntrospectInfo);
|
||||
implement_into_ffi_by_protobuf!(msg_types::Device);
|
||||
implement_into_ffi_by_delegation!(Device, msg_types::Device);
|
||||
implement_into_ffi_by_protobuf!(msg_types::Devices);
|
||||
implement_into_ffi_by_delegation!(AccountEvent, msg_types::AccountEvent);
|
||||
implement_into_ffi_by_protobuf!(msg_types::AccountEvent);
|
||||
implement_into_ffi_by_protobuf!(msg_types::AccountEvents);
|
||||
implement_into_ffi_by_delegation!(IncomingDeviceCommand, msg_types::IncomingDeviceCommand);
|
||||
implement_into_ffi_by_protobuf!(msg_types::IncomingDeviceCommand);
|
||||
implement_into_ffi_by_protobuf!(msg_types::IncomingDeviceCommands);
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,142 +0,0 @@
|
|||
syntax = "proto2";
|
||||
|
||||
// Note: this file name must be unique due to how the iOS megazord works :(
|
||||
|
||||
package mozilla.appservices.fxaclient.protobuf;
|
||||
|
||||
option java_package = "mozilla.appservices.fxaclient";
|
||||
option java_outer_classname = "MsgTypes";
|
||||
option swift_prefix = "MsgTypes_";
|
||||
option optimize_for = LITE_RUNTIME;
|
||||
|
||||
message Profile {
|
||||
optional string uid = 1;
|
||||
optional string email = 2;
|
||||
optional string avatar = 3;
|
||||
optional bool avatar_default = 4;
|
||||
optional string display_name = 5;
|
||||
}
|
||||
|
||||
message AccessTokenInfo {
|
||||
required string scope = 1;
|
||||
required string token = 2;
|
||||
optional ScopedKey key = 3;
|
||||
required uint64 expires_at = 4;
|
||||
}
|
||||
|
||||
message IntrospectInfo {
|
||||
required bool active = 1;
|
||||
}
|
||||
|
||||
message ScopedKey {
|
||||
required string kty = 1;
|
||||
required string scope = 2;
|
||||
required string k = 3;
|
||||
required string kid = 4;
|
||||
}
|
||||
|
||||
message Device {
|
||||
message PushSubscription {
|
||||
required string endpoint = 1;
|
||||
required string public_key = 2;
|
||||
required string auth_key = 3;
|
||||
}
|
||||
enum Capability {
|
||||
SEND_TAB = 1;
|
||||
}
|
||||
enum Type {
|
||||
DESKTOP = 1;
|
||||
MOBILE = 2;
|
||||
TABLET = 3;
|
||||
VR = 4;
|
||||
TV = 5;
|
||||
UNKNOWN = 6;
|
||||
}
|
||||
required string id = 1;
|
||||
required string display_name = 2;
|
||||
required Type type = 3;
|
||||
optional PushSubscription push_subscription = 4;
|
||||
required bool push_endpoint_expired = 5;
|
||||
required bool is_current_device = 6;
|
||||
optional uint64 last_access_time = 7;
|
||||
repeated Capability capabilities = 8;
|
||||
}
|
||||
|
||||
message Devices {
|
||||
repeated Device devices = 1;
|
||||
}
|
||||
|
||||
message Capabilities {
|
||||
repeated Device.Capability capability = 1;
|
||||
}
|
||||
|
||||
message IncomingDeviceCommand {
|
||||
enum IncomingDeviceCommandType {
|
||||
TAB_RECEIVED = 1; // `data` set to `tab_received_data`.
|
||||
}
|
||||
required IncomingDeviceCommandType type = 1;
|
||||
|
||||
message SendTabData {
|
||||
message TabHistoryEntry {
|
||||
required string title = 1;
|
||||
required string url = 2;
|
||||
}
|
||||
optional Device from = 1;
|
||||
repeated TabHistoryEntry entries = 2;
|
||||
}
|
||||
|
||||
oneof data {
|
||||
SendTabData tab_received_data = 2;
|
||||
};
|
||||
}
|
||||
|
||||
message IncomingDeviceCommands {
|
||||
repeated IncomingDeviceCommand commands = 1;
|
||||
}
|
||||
|
||||
// This is basically an enum with associated values,
|
||||
// but it's a bit harder to model in proto2.
|
||||
message AccountEvent {
|
||||
enum AccountEventType {
|
||||
INCOMING_DEVICE_COMMAND = 1; // `data` set to `device_command`.
|
||||
PROFILE_UPDATED = 2;
|
||||
DEVICE_CONNECTED = 3; // `data` set to `device_connected_name`.
|
||||
ACCOUNT_AUTH_STATE_CHANGED = 4;
|
||||
DEVICE_DISCONNECTED = 5; // `data` set to `device_disconnected_data`.
|
||||
ACCOUNT_DESTROYED = 6;
|
||||
}
|
||||
required AccountEventType type = 1;
|
||||
|
||||
message DeviceDisconnectedData {
|
||||
required string device_id = 1;
|
||||
required bool is_local_device = 2;
|
||||
}
|
||||
|
||||
oneof data {
|
||||
IncomingDeviceCommand device_command = 2;
|
||||
string device_connected_name = 3;
|
||||
DeviceDisconnectedData device_disconnected_data = 4;
|
||||
}
|
||||
}
|
||||
|
||||
message AccountEvents {
|
||||
repeated AccountEvent events = 1;
|
||||
}
|
||||
|
||||
message AuthorizationPKCEParams {
|
||||
required string code_challenge = 1;
|
||||
required string code_challenge_method = 2;
|
||||
}
|
||||
|
||||
message AuthorizationParams {
|
||||
required string client_id = 1;
|
||||
required string scope = 2;
|
||||
required string state = 3;
|
||||
required string access_type = 4;
|
||||
optional AuthorizationPKCEParams pkce_params = 5;
|
||||
optional string keys_jwk = 6;
|
||||
}
|
||||
|
||||
message MetricsParams {
|
||||
map<string, string> parameters = 1;
|
||||
}
|
|
@ -2,8 +2,8 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
pub use crate::oauth::{AuthorizationPKCEParams, AuthorizationParameters};
|
||||
use crate::{error::*, http_client, scoped_keys::ScopedKey, util::Xorable, Config};
|
||||
pub use super::oauth::AuthorizationParameters;
|
||||
use super::{error::*, http_client, scoped_keys::ScopedKey, util::Xorable, Config};
|
||||
pub use http_client::{
|
||||
derive_auth_key_from_session_token, send_authorization_request, send_verification,
|
||||
AuthorizationRequestParameters,
|
|
@ -0,0 +1,34 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::convert::TryFrom;
|
||||
|
||||
pub mod send_tab;
|
||||
pub use send_tab::SendTabPayload;
|
||||
|
||||
use super::device::Device;
|
||||
use super::error::*;
|
||||
|
||||
// Currently public for use by example crates, but should be made private eventually.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum IncomingDeviceCommand {
|
||||
TabReceived {
|
||||
sender: Option<Device>,
|
||||
payload: SendTabPayload,
|
||||
},
|
||||
}
|
||||
|
||||
impl TryFrom<IncomingDeviceCommand> for crate::IncomingDeviceCommand {
|
||||
type Error = Error;
|
||||
fn try_from(cmd: IncomingDeviceCommand) -> Result<Self> {
|
||||
Ok(match cmd {
|
||||
IncomingDeviceCommand::TabReceived { sender, payload } => {
|
||||
crate::IncomingDeviceCommand::TabReceived {
|
||||
sender: sender.map(crate::Device::try_from).transpose()?,
|
||||
payload: payload.into(),
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -13,12 +13,14 @@
|
|||
/// uses the obtained public key to encrypt the `SendTabPayload` it created that
|
||||
/// contains the tab to send and finally forms the `EncryptedSendTabPayload` that is
|
||||
/// then sent to the target device.
|
||||
use crate::{device::Device, error::*, scoped_keys::ScopedKey, scopes, telemetry};
|
||||
use serde_derive::*;
|
||||
|
||||
use rc_crypto::ece::{self, Aes128GcmEceWebPush, EcKeyComponents, WebPushParams};
|
||||
use rc_crypto::ece_crypto::{RcCryptoLocalKeyPair, RcCryptoRemotePublicKey};
|
||||
use serde_derive::*;
|
||||
use sync15::{EncryptedPayload, KeyBundle};
|
||||
|
||||
use super::super::{device::Device, error::*, scoped_keys::ScopedKey, scopes, telemetry};
|
||||
|
||||
pub const COMMAND_NAME: &str = "https://identity.mozilla.com/cmd/open-uri";
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
|
@ -37,7 +39,7 @@ impl EncryptedSendTabPayload {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct SendTabPayload {
|
||||
pub entries: Vec<TabHistoryEntry>,
|
||||
#[serde(rename = "flowID", default)]
|
||||
|
@ -46,6 +48,16 @@ pub struct SendTabPayload {
|
|||
pub stream_id: String,
|
||||
}
|
||||
|
||||
impl From<SendTabPayload> for crate::SendTabPayload {
|
||||
fn from(payload: SendTabPayload) -> Self {
|
||||
crate::SendTabPayload {
|
||||
entries: payload.entries.into_iter().map(From::from).collect(),
|
||||
flow_id: payload.flow_id,
|
||||
stream_id: payload.stream_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SendTabPayload {
|
||||
pub fn single_tab(title: &str, url: &str) -> (Self, telemetry::SentCommand) {
|
||||
let sent_telemetry: telemetry::SentCommand = Default::default();
|
||||
|
@ -78,12 +90,21 @@ impl SendTabPayload {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct TabHistoryEntry {
|
||||
pub title: String,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
impl From<TabHistoryEntry> for crate::TabHistoryEntry {
|
||||
fn from(e: TabHistoryEntry) -> Self {
|
||||
crate::TabHistoryEntry {
|
||||
title: e.title,
|
||||
url: e.url,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub(crate) enum VersionnedPrivateSendTabKeys {
|
||||
V1(PrivateSendTabKeysV1),
|
|
@ -2,7 +2,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use crate::{error::*, http_client};
|
||||
use super::{error::*, http_client};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::{cell::RefCell, sync::Arc};
|
||||
use url::Url;
|
|
@ -2,19 +2,22 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
pub use crate::http_client::{
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
convert::TryFrom,
|
||||
};
|
||||
|
||||
use serde_derive::*;
|
||||
|
||||
pub use super::http_client::{
|
||||
DeviceLocation as Location, DeviceType as Type, GetDeviceResponse as Device, PushSubscription,
|
||||
};
|
||||
use crate::{
|
||||
commands,
|
||||
use super::{
|
||||
commands::{self, IncomingDeviceCommand},
|
||||
error::*,
|
||||
http_client::{
|
||||
DeviceUpdateRequest, DeviceUpdateRequestBuilder, PendingCommand, UpdateDeviceResponse,
|
||||
},
|
||||
telemetry, util, CachedResponse, FirefoxAccount, IncomingDeviceCommand,
|
||||
http_client::{DeviceUpdateRequest, DeviceUpdateRequestBuilder, PendingCommand},
|
||||
telemetry, util, CachedResponse, FirefoxAccount,
|
||||
};
|
||||
use serde_derive::*;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
// An devices response is considered fresh for `DEVICES_FRESHNESS_THRESHOLD` ms.
|
||||
const DEVICES_FRESHNESS_THRESHOLD: u64 = 60_000; // 1 minute
|
||||
|
@ -42,7 +45,9 @@ impl FirefoxAccount {
|
|||
}
|
||||
|
||||
let refresh_token = self.get_refresh_token()?;
|
||||
let response = self.client.devices(&self.state.config, &refresh_token)?;
|
||||
let response = self
|
||||
.client
|
||||
.get_devices(&self.state.config, &refresh_token)?;
|
||||
|
||||
self.devices_cache = Some(CachedResponse {
|
||||
response: response.clone(),
|
||||
|
@ -98,15 +103,13 @@ impl FirefoxAccount {
|
|||
device_type: Type,
|
||||
capabilities: &[Capability],
|
||||
) -> Result<()> {
|
||||
let commands = self.register_capabilities(capabilities)?;
|
||||
let commands = self.register_capabilities(&capabilities)?;
|
||||
let update = DeviceUpdateRequestBuilder::new()
|
||||
.display_name(name)
|
||||
.device_type(&device_type)
|
||||
.available_commands(&commands)
|
||||
.build();
|
||||
let resp = self.update_device(update)?;
|
||||
self.state.current_device_id = Option::from(resp.id);
|
||||
Ok(())
|
||||
self.update_device(update)
|
||||
}
|
||||
|
||||
/// Register a set of device capabilities against the current device.
|
||||
|
@ -130,13 +133,11 @@ impl FirefoxAccount {
|
|||
{
|
||||
return Ok(());
|
||||
}
|
||||
let commands = self.register_capabilities(capabilities)?;
|
||||
let commands = self.register_capabilities(&capabilities)?;
|
||||
let update = DeviceUpdateRequestBuilder::new()
|
||||
.available_commands(&commands)
|
||||
.build();
|
||||
let resp = self.update_device(update)?;
|
||||
self.state.current_device_id = Option::from(resp.id);
|
||||
Ok(())
|
||||
self.update_device(update)
|
||||
}
|
||||
|
||||
/// Re-register the device capabilities, this should only be used internally.
|
||||
|
@ -215,7 +216,7 @@ impl FirefoxAccount {
|
|||
let refresh_token = self.get_refresh_token()?;
|
||||
let pending_commands =
|
||||
self.client
|
||||
.pending_commands(&self.state.config, refresh_token, index, limit)?;
|
||||
.get_pending_commands(&self.state.config, refresh_token, index, limit)?;
|
||||
if pending_commands.messages.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
@ -269,22 +270,19 @@ impl FirefoxAccount {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn set_device_name(&mut self, name: &str) -> Result<UpdateDeviceResponse> {
|
||||
pub fn set_device_name(&mut self, name: &str) -> Result<()> {
|
||||
let update = DeviceUpdateRequestBuilder::new().display_name(name).build();
|
||||
self.update_device(update)
|
||||
}
|
||||
|
||||
pub fn clear_device_name(&mut self) -> Result<UpdateDeviceResponse> {
|
||||
pub fn clear_device_name(&mut self) -> Result<()> {
|
||||
let update = DeviceUpdateRequestBuilder::new()
|
||||
.clear_display_name()
|
||||
.build();
|
||||
self.update_device(update)
|
||||
}
|
||||
|
||||
pub fn set_push_subscription(
|
||||
&mut self,
|
||||
push_subscription: &PushSubscription,
|
||||
) -> Result<UpdateDeviceResponse> {
|
||||
pub fn set_push_subscription(&mut self, push_subscription: PushSubscription) -> Result<()> {
|
||||
let update = DeviceUpdateRequestBuilder::new()
|
||||
.push_subscription(&push_subscription)
|
||||
.build();
|
||||
|
@ -295,11 +293,7 @@ impl FirefoxAccount {
|
|||
// for the device because the server does not have a `PATCH commands`
|
||||
// endpoint yet.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn register_command(
|
||||
&mut self,
|
||||
command: &str,
|
||||
value: &str,
|
||||
) -> Result<UpdateDeviceResponse> {
|
||||
pub(crate) fn register_command(&mut self, command: &str, value: &str) -> Result<()> {
|
||||
self.state.device_capabilities.clear();
|
||||
let mut commands = HashMap::new();
|
||||
commands.insert(command.to_owned(), value.to_owned());
|
||||
|
@ -312,7 +306,7 @@ impl FirefoxAccount {
|
|||
// TODO: this currently deletes every command registered for the device
|
||||
// because the server does not have a `PATCH commands` endpoint yet.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn unregister_command(&mut self, _: &str) -> Result<UpdateDeviceResponse> {
|
||||
pub(crate) fn unregister_command(&mut self, _: &str) -> Result<()> {
|
||||
self.state.device_capabilities.clear();
|
||||
let commands = HashMap::new();
|
||||
let update = DeviceUpdateRequestBuilder::new()
|
||||
|
@ -322,7 +316,7 @@ impl FirefoxAccount {
|
|||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn clear_commands(&mut self) -> Result<UpdateDeviceResponse> {
|
||||
pub(crate) fn clear_commands(&mut self) -> Result<()> {
|
||||
self.state.device_capabilities.clear();
|
||||
let update = DeviceUpdateRequestBuilder::new()
|
||||
.clear_available_commands()
|
||||
|
@ -336,7 +330,7 @@ impl FirefoxAccount {
|
|||
device_type: &Type,
|
||||
push_subscription: &Option<PushSubscription>,
|
||||
commands: &HashMap<String, String>,
|
||||
) -> Result<UpdateDeviceResponse> {
|
||||
) -> Result<()> {
|
||||
self.state.device_capabilities.clear();
|
||||
let mut builder = DeviceUpdateRequestBuilder::new()
|
||||
.display_name(display_name)
|
||||
|
@ -348,13 +342,16 @@ impl FirefoxAccount {
|
|||
self.update_device(builder.build())
|
||||
}
|
||||
|
||||
fn update_device(&mut self, update: DeviceUpdateRequest<'_>) -> Result<UpdateDeviceResponse> {
|
||||
fn update_device(&mut self, update: DeviceUpdateRequest<'_>) -> Result<()> {
|
||||
let refresh_token = self.get_refresh_token()?;
|
||||
let res = self
|
||||
.client
|
||||
.update_device(&self.state.config, refresh_token, update);
|
||||
.update_device_record(&self.state.config, refresh_token, update);
|
||||
match res {
|
||||
Ok(resp) => Ok(resp),
|
||||
Ok(resp) => {
|
||||
self.state.current_device_id = Option::from(resp.id);
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => {
|
||||
// We failed to write an update to the server.
|
||||
// Clear local state so that we'll be sure to retry later.
|
||||
|
@ -378,13 +375,80 @@ pub enum Capability {
|
|||
SendTab,
|
||||
}
|
||||
|
||||
impl From<crate::DeviceCapability> for Capability {
|
||||
fn from(cap: crate::DeviceCapability) -> Self {
|
||||
match cap {
|
||||
crate::DeviceCapability::SendTab => Capability::SendTab,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Capability> for crate::DeviceCapability {
|
||||
fn from(cap: Capability) -> Self {
|
||||
match cap {
|
||||
Capability::SendTab => crate::DeviceCapability::SendTab,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Device> for crate::Device {
|
||||
type Error = Error;
|
||||
fn try_from(d: Device) -> Result<Self> {
|
||||
let capabilities: Vec<_> = d
|
||||
.available_commands
|
||||
.keys()
|
||||
.filter_map(|k| match k.as_str() {
|
||||
commands::send_tab::COMMAND_NAME => Some(Capability::SendTab),
|
||||
_ => None,
|
||||
})
|
||||
.map(Into::into)
|
||||
.collect();
|
||||
Ok(crate::Device {
|
||||
id: d.common.id,
|
||||
display_name: d.common.display_name,
|
||||
device_type: d.common.device_type.into(),
|
||||
capabilities,
|
||||
push_subscription: d.common.push_subscription.map(Into::into),
|
||||
push_endpoint_expired: d.common.push_endpoint_expired,
|
||||
is_current_device: d.is_current_device,
|
||||
last_access_time: d.last_access_time.map(TryFrom::try_from).transpose()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Type> for crate::DeviceType {
|
||||
fn from(type_: Type) -> Self {
|
||||
match type_ {
|
||||
Type::Desktop => crate::DeviceType::Desktop,
|
||||
Type::Mobile => crate::DeviceType::Mobile,
|
||||
Type::Tablet => crate::DeviceType::Tablet,
|
||||
Type::VR => crate::DeviceType::VR,
|
||||
Type::TV => crate::DeviceType::TV,
|
||||
Type::Unknown => crate::DeviceType::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::DeviceType> for Type {
|
||||
fn from(type_: crate::DeviceType) -> Self {
|
||||
match type_ {
|
||||
crate::DeviceType::Desktop => Type::Desktop,
|
||||
crate::DeviceType::Mobile => Type::Mobile,
|
||||
crate::DeviceType::Tablet => Type::Tablet,
|
||||
crate::DeviceType::VR => Type::VR,
|
||||
crate::DeviceType::TV => Type::TV,
|
||||
crate::DeviceType::Unknown => Type::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::http_client::*;
|
||||
use crate::oauth::RefreshToken;
|
||||
use crate::scoped_keys::ScopedKey;
|
||||
use crate::Config;
|
||||
use crate::internal::http_client::*;
|
||||
use crate::internal::oauth::RefreshToken;
|
||||
use crate::internal::scoped_keys::ScopedKey;
|
||||
use crate::internal::Config;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
@ -413,7 +477,7 @@ mod tests {
|
|||
// Do an initial call to ensure_capabilities().
|
||||
let mut client = FxAClientMock::new();
|
||||
client
|
||||
.expect_update_device(
|
||||
.expect_update_device_record(
|
||||
mockiato::Argument::any,
|
||||
|arg| arg.partial_eq("refreshtok"),
|
||||
mockiato::Argument::any,
|
||||
|
@ -450,7 +514,7 @@ mod tests {
|
|||
// Do an initial call to ensure_capabilities().
|
||||
let mut client = FxAClientMock::new();
|
||||
client
|
||||
.expect_update_device(
|
||||
.expect_update_device_record(
|
||||
mockiato::Argument::any,
|
||||
|arg| arg.partial_eq("refreshtok"),
|
||||
mockiato::Argument::any,
|
||||
|
@ -471,7 +535,7 @@ mod tests {
|
|||
// Do another call with reduced capabilities.
|
||||
let mut client = FxAClientMock::new();
|
||||
client
|
||||
.expect_update_device(
|
||||
.expect_update_device_record(
|
||||
mockiato::Argument::any,
|
||||
|arg| arg.partial_eq("refreshtok"),
|
||||
mockiato::Argument::any,
|
||||
|
@ -493,7 +557,7 @@ mod tests {
|
|||
let mut restored = FirefoxAccount::from_json(&saved).unwrap();
|
||||
let mut client = FxAClientMock::new();
|
||||
client
|
||||
.expect_update_device(
|
||||
.expect_update_device_record(
|
||||
mockiato::Argument::any,
|
||||
|arg| arg.partial_eq("refreshtok"),
|
||||
mockiato::Argument::any,
|
||||
|
@ -520,7 +584,7 @@ mod tests {
|
|||
// Do an initial call to ensure_capabilities().
|
||||
let mut client = FxAClientMock::new();
|
||||
client
|
||||
.expect_update_device(
|
||||
.expect_update_device_record(
|
||||
mockiato::Argument::any,
|
||||
|arg| arg.partial_eq("refreshtok"),
|
||||
mockiato::Argument::any,
|
||||
|
@ -541,7 +605,7 @@ mod tests {
|
|||
// Do another call with reduced capabilities.
|
||||
let mut client = FxAClientMock::new();
|
||||
client
|
||||
.expect_update_device(
|
||||
.expect_update_device_record(
|
||||
mockiato::Argument::any,
|
||||
|arg| arg.partial_eq("refreshtok"),
|
||||
mockiato::Argument::any,
|
||||
|
@ -563,7 +627,7 @@ mod tests {
|
|||
let mut restored = FirefoxAccount::from_json(&saved).unwrap();
|
||||
let mut client = FxAClientMock::new();
|
||||
client
|
||||
.expect_update_device(
|
||||
.expect_update_device_record(
|
||||
mockiato::Argument::any,
|
||||
|arg| arg.partial_eq("refreshtok"),
|
||||
mockiato::Argument::any,
|
||||
|
@ -588,7 +652,7 @@ mod tests {
|
|||
// Do an initial call to ensure_capabilities().
|
||||
let mut client = FxAClientMock::new();
|
||||
client
|
||||
.expect_update_device(
|
||||
.expect_update_device_record(
|
||||
mockiato::Argument::any,
|
||||
|arg| arg.partial_eq("refreshtok"),
|
||||
mockiato::Argument::any,
|
||||
|
@ -618,7 +682,7 @@ mod tests {
|
|||
}
|
||||
.into()));
|
||||
client
|
||||
.expect_devices(mockiato::Argument::any, mockiato::Argument::any)
|
||||
.expect_get_devices(mockiato::Argument::any, mockiato::Argument::any)
|
||||
.returns_once(Err(ErrorKind::RemoteError {
|
||||
code: 500,
|
||||
errno: 999,
|
||||
|
@ -658,7 +722,7 @@ mod tests {
|
|||
// It should re-register, as server-side state may have changed.
|
||||
let mut client = FxAClientMock::new();
|
||||
client
|
||||
.expect_update_device(
|
||||
.expect_update_device_record(
|
||||
mockiato::Argument::any,
|
||||
|arg| arg.partial_eq("newRefreshTok"),
|
||||
mockiato::Argument::any,
|
||||
|
@ -682,7 +746,7 @@ mod tests {
|
|||
// Do an initial call to ensure_capabilities(), that fails.
|
||||
let mut client = FxAClientMock::new();
|
||||
client
|
||||
.expect_update_device(
|
||||
.expect_update_device_record(
|
||||
mockiato::Argument::any,
|
||||
|arg| arg.partial_eq("refreshtok"),
|
||||
mockiato::Argument::any,
|
||||
|
@ -702,7 +766,7 @@ mod tests {
|
|||
// Do another call, which should re-attempt the update.
|
||||
let mut client = FxAClientMock::new();
|
||||
client
|
||||
.expect_update_device(
|
||||
.expect_update_device_record(
|
||||
mockiato::Argument::any,
|
||||
|arg| arg.partial_eq("refreshtok"),
|
||||
mockiato::Argument::any,
|
||||
|
@ -725,7 +789,7 @@ mod tests {
|
|||
let mut fxa = setup();
|
||||
let mut client = FxAClientMock::new();
|
||||
client
|
||||
.expect_devices(mockiato::Argument::any, mockiato::Argument::any)
|
||||
.expect_get_devices(mockiato::Argument::any, mockiato::Argument::any)
|
||||
.times(1)
|
||||
.returns_once(Ok(vec![Device {
|
||||
common: DeviceResponseCommon {
|
||||
|
@ -776,7 +840,7 @@ mod tests {
|
|||
let mut fxa = setup();
|
||||
let mut client = FxAClientMock::new();
|
||||
client
|
||||
.expect_devices(mockiato::Argument::any, mockiato::Argument::any)
|
||||
.expect_get_devices(mockiato::Argument::any, mockiato::Argument::any)
|
||||
.times(1)
|
||||
.returns_once(Err(ErrorKind::RemoteError {
|
||||
code: 500,
|
|
@ -121,8 +121,8 @@ pub enum ErrorKind {
|
|||
#[error("HAWK error: {0}")]
|
||||
HawkError(#[from] hawk::Error),
|
||||
|
||||
#[error("Protobuf decode error: {0}")]
|
||||
ProtobufDecodeError(#[from] prost::DecodeError),
|
||||
#[error("Integer conversion error: {0}")]
|
||||
IntegerConversionError(#[from] std::num::TryFromIntError),
|
||||
}
|
||||
|
||||
error_support::define_error! {
|
||||
|
@ -138,12 +138,37 @@ error_support::define_error! {
|
|||
(UnexpectedStatus, viaduct::UnexpectedStatus),
|
||||
(MalformedUrl, url::ParseError),
|
||||
(SyncError, sync15::Error),
|
||||
(ProtobufDecodeError, prost::DecodeError),
|
||||
}
|
||||
}
|
||||
|
||||
error_support::define_error_conversions! {
|
||||
ErrorKind {
|
||||
(HawkError, hawk::Error),
|
||||
(IntegerConversionError, std::num::TryFromIntError),
|
||||
}
|
||||
}
|
||||
|
||||
// The public FFI puts the errors into three buckets, this helps us
|
||||
// convert between them. Maybe in future we can use uniffi to expose
|
||||
// more error info to the caller?
|
||||
impl From<super::Error> for crate::FxaError {
|
||||
fn from(err: super::Error) -> crate::FxaError {
|
||||
match err.kind() {
|
||||
super::ErrorKind::RemoteError { code: 401, .. }
|
||||
| super::ErrorKind::NoRefreshToken
|
||||
| super::ErrorKind::NoScopedKey(_)
|
||||
| super::ErrorKind::NoCachedToken(_) => {
|
||||
log::warn!("Authentication error: {:?}", err);
|
||||
crate::FxaError::Authentication
|
||||
}
|
||||
super::ErrorKind::RequestError(_) => {
|
||||
log::warn!("Network error: {:?}", err);
|
||||
crate::FxaError::Network
|
||||
}
|
||||
_ => {
|
||||
log::warn!("Unexpected error: {:?}", err);
|
||||
crate::FxaError::Other
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,7 +2,13 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use crate::{config::Config, error::*};
|
||||
//! Low-level API for talking to the FxA server.
|
||||
//!
|
||||
//! This module is responsible for talking to the FxA server over HTTP,
|
||||
//! serializing request bodies and deserializing response payloads into
|
||||
//! live objects that can be inspected by other parts of the code.
|
||||
|
||||
use super::{config::Config, error::*};
|
||||
use rc_crypto::{
|
||||
digest,
|
||||
hawk::{Credentials, Key, PayloadHasher, RequestBuilder, SHA256},
|
||||
|
@ -22,58 +28,79 @@ const HAWK_HKDF_SALT: [u8; 32] = [0b0; 32];
|
|||
const HAWK_KEY_LENGTH: usize = 32;
|
||||
const RETRY_AFTER_DEFAULT_SECONDS: u64 = 10;
|
||||
|
||||
/// Trait defining the low-level API for talking to the FxA server.
|
||||
///
|
||||
/// These are all the methods and datatypes used for interacting with
|
||||
/// the FxA server. It's defined as a trait mostly to make it mockable
|
||||
/// for testing purposes.
|
||||
///
|
||||
/// The default live implementation of this trait can be found in
|
||||
/// the [`Client`] struct, and you should consult that struct for
|
||||
/// documentation on specific methods.
|
||||
///
|
||||
/// A brief note on names: you'll see that the method names on this trait
|
||||
/// try to follow a pattern of `<verb>_<noun>[_<additional info>]()`.
|
||||
/// Please try to keep to this pattern if you add methods to this trait.
|
||||
///
|
||||
/// Consistent names are helpful at the best of times, but they're
|
||||
/// particularly important here because so many of the nouns in OAuth
|
||||
/// contain embedded verbs! For example: at a glance, does a method
|
||||
/// named `refresh_token` refresh something called a "token", or does
|
||||
/// it return something called a "refresh token"? Using unambiguous
|
||||
/// verbs to start each method helps avoid confusion here.
|
||||
///
|
||||
#[cfg_attr(test, mockiato::mockable)]
|
||||
pub(crate) trait FxAClient {
|
||||
fn refresh_token_with_code(
|
||||
fn create_refresh_token_using_authorization_code(
|
||||
&self,
|
||||
config: &Config,
|
||||
code: &str,
|
||||
code_verifier: &str,
|
||||
) -> Result<OAuthTokenResponse>;
|
||||
fn refresh_token_with_session_token(
|
||||
fn create_refresh_token_using_session_token(
|
||||
&self,
|
||||
config: &Config,
|
||||
session_token: &str,
|
||||
scopes: &[&str],
|
||||
) -> Result<OAuthTokenResponse>;
|
||||
fn oauth_introspect_refresh_token(
|
||||
fn check_refresh_token_status(
|
||||
&self,
|
||||
config: &Config,
|
||||
refresh_token: &str,
|
||||
) -> Result<IntrospectResponse>;
|
||||
fn access_token_with_refresh_token(
|
||||
fn create_access_token_using_refresh_token(
|
||||
&self,
|
||||
config: &Config,
|
||||
refresh_token: &str,
|
||||
ttl: Option<u64>,
|
||||
scopes: &[&str],
|
||||
) -> Result<OAuthTokenResponse>;
|
||||
fn access_token_with_session_token(
|
||||
fn create_access_token_using_session_token(
|
||||
&self,
|
||||
config: &Config,
|
||||
session_token: &str,
|
||||
scopes: &[&str],
|
||||
) -> Result<OAuthTokenResponse>;
|
||||
fn authorization_code_using_session_token(
|
||||
fn create_authorization_code_using_session_token(
|
||||
&self,
|
||||
config: &Config,
|
||||
session_token: &str,
|
||||
auth_params: AuthorizationRequestParameters,
|
||||
) -> Result<OAuthAuthResponse>;
|
||||
fn duplicate_session(
|
||||
fn duplicate_session_token(
|
||||
&self,
|
||||
config: &Config,
|
||||
session_token: &str,
|
||||
) -> Result<DuplicateTokenResponse>;
|
||||
fn destroy_access_token(&self, config: &Config, token: &str) -> Result<()>;
|
||||
fn destroy_refresh_token(&self, config: &Config, token: &str) -> Result<()>;
|
||||
fn profile(
|
||||
fn get_profile(
|
||||
&self,
|
||||
config: &Config,
|
||||
profile_access_token: &str,
|
||||
etag: Option<String>,
|
||||
) -> Result<Option<ResponseAndETag<ProfileResponse>>>;
|
||||
fn pending_commands(
|
||||
fn get_pending_commands(
|
||||
&self,
|
||||
config: &Config,
|
||||
refresh_token: &str,
|
||||
|
@ -88,28 +115,28 @@ pub(crate) trait FxAClient {
|
|||
target: &str,
|
||||
payload: &serde_json::Value,
|
||||
) -> Result<()>;
|
||||
fn devices(&self, config: &Config, refresh_token: &str) -> Result<Vec<GetDeviceResponse>>;
|
||||
fn update_device(
|
||||
fn update_device_record(
|
||||
&self,
|
||||
config: &Config,
|
||||
refresh_token: &str,
|
||||
update: DeviceUpdateRequest<'_>,
|
||||
) -> Result<UpdateDeviceResponse>;
|
||||
fn destroy_device(&self, config: &Config, refresh_token: &str, id: &str) -> Result<()>;
|
||||
fn attached_clients(
|
||||
fn destroy_device_record(&self, config: &Config, refresh_token: &str, id: &str) -> Result<()>;
|
||||
fn get_devices(&self, config: &Config, refresh_token: &str) -> Result<Vec<GetDeviceResponse>>;
|
||||
fn get_attached_clients(
|
||||
&self,
|
||||
config: &Config,
|
||||
session_token: &str,
|
||||
) -> Result<Vec<GetAttachedClientResponse>>;
|
||||
fn scoped_key_data(
|
||||
fn get_scoped_key_data(
|
||||
&self,
|
||||
config: &Config,
|
||||
session_token: &str,
|
||||
client_id: &str,
|
||||
scope: &str,
|
||||
) -> Result<HashMap<String, ScopedKeyDataResponse>>;
|
||||
fn fxa_client_configuration(&self, config: &Config) -> Result<ClientConfigurationResponse>;
|
||||
fn openid_configuration(&self, config: &Config) -> Result<OpenIdConfigurationResponse>;
|
||||
fn get_fxa_client_configuration(&self, config: &Config) -> Result<ClientConfigurationResponse>;
|
||||
fn get_openid_configuration(&self, config: &Config) -> Result<OpenIdConfigurationResponse>;
|
||||
}
|
||||
|
||||
enum HttpClientState {
|
||||
|
@ -124,17 +151,17 @@ pub struct Client {
|
|||
state: Mutex<HashMap<String, HttpClientState>>,
|
||||
}
|
||||
impl FxAClient for Client {
|
||||
fn fxa_client_configuration(&self, config: &Config) -> Result<ClientConfigurationResponse> {
|
||||
fn get_fxa_client_configuration(&self, config: &Config) -> Result<ClientConfigurationResponse> {
|
||||
// Why go through two-levels of indirection? It looks kinda dumb.
|
||||
// Well, `config:Config` also needs to fetch the config, but does not have access
|
||||
// to an instance of `http_client`, so it calls the helper function directly.
|
||||
fxa_client_configuration(config.client_config_url()?)
|
||||
}
|
||||
fn openid_configuration(&self, config: &Config) -> Result<OpenIdConfigurationResponse> {
|
||||
fn get_openid_configuration(&self, config: &Config) -> Result<OpenIdConfigurationResponse> {
|
||||
openid_configuration(config.openid_config_url()?)
|
||||
}
|
||||
|
||||
fn profile(
|
||||
fn get_profile(
|
||||
&self,
|
||||
config: &Config,
|
||||
access_token: &str,
|
||||
|
@ -160,8 +187,7 @@ impl FxAClient for Client {
|
|||
}))
|
||||
}
|
||||
|
||||
// For the one-off generation of a `refresh_token` and associated meta from transient credentials.
|
||||
fn refresh_token_with_code(
|
||||
fn create_refresh_token_using_authorization_code(
|
||||
&self,
|
||||
config: &Config,
|
||||
code: &str,
|
||||
|
@ -176,7 +202,7 @@ impl FxAClient for Client {
|
|||
self.make_oauth_token_request(config, serde_json::to_value(req_body).unwrap())
|
||||
}
|
||||
|
||||
fn refresh_token_with_session_token(
|
||||
fn create_refresh_token_using_session_token(
|
||||
&self,
|
||||
config: &Config,
|
||||
session_token: &str,
|
||||
|
@ -198,7 +224,7 @@ impl FxAClient for Client {
|
|||
|
||||
// For the regular generation of an `access_token` from long-lived credentials.
|
||||
|
||||
fn access_token_with_refresh_token(
|
||||
fn create_access_token_using_refresh_token(
|
||||
&self,
|
||||
config: &Config,
|
||||
refresh_token: &str,
|
||||
|
@ -214,7 +240,7 @@ impl FxAClient for Client {
|
|||
self.make_oauth_token_request(config, serde_json::to_value(req).unwrap())
|
||||
}
|
||||
|
||||
fn access_token_with_session_token(
|
||||
fn create_access_token_using_session_token(
|
||||
&self,
|
||||
config: &Config,
|
||||
session_token: &str,
|
||||
|
@ -233,7 +259,7 @@ impl FxAClient for Client {
|
|||
self.make_request(request)?.json().map_err(Into::into)
|
||||
}
|
||||
|
||||
fn authorization_code_using_session_token(
|
||||
fn create_authorization_code_using_session_token(
|
||||
&self,
|
||||
config: &Config,
|
||||
session_token: &str,
|
||||
|
@ -249,7 +275,7 @@ impl FxAClient for Client {
|
|||
Ok(self.make_request(request)?.json()?)
|
||||
}
|
||||
|
||||
fn oauth_introspect_refresh_token(
|
||||
fn check_refresh_token_status(
|
||||
&self,
|
||||
config: &Config,
|
||||
refresh_token: &str,
|
||||
|
@ -262,7 +288,7 @@ impl FxAClient for Client {
|
|||
Ok(self.make_request(Request::post(url).json(&body))?.json()?)
|
||||
}
|
||||
|
||||
fn duplicate_session(
|
||||
fn duplicate_session_token(
|
||||
&self,
|
||||
config: &Config,
|
||||
session_token: &str,
|
||||
|
@ -293,7 +319,7 @@ impl FxAClient for Client {
|
|||
self.destroy_token_helper(config, &body)
|
||||
}
|
||||
|
||||
fn pending_commands(
|
||||
fn get_pending_commands(
|
||||
&self,
|
||||
config: &Config,
|
||||
refresh_token: &str,
|
||||
|
@ -332,14 +358,14 @@ impl FxAClient for Client {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn devices(&self, config: &Config, refresh_token: &str) -> Result<Vec<GetDeviceResponse>> {
|
||||
fn get_devices(&self, config: &Config, refresh_token: &str) -> Result<Vec<GetDeviceResponse>> {
|
||||
let url = config.auth_url_path("v1/account/devices")?;
|
||||
let request =
|
||||
Request::get(url).header(header_names::AUTHORIZATION, bearer_token(refresh_token))?;
|
||||
Ok(self.make_request(request)?.json()?)
|
||||
}
|
||||
|
||||
fn update_device(
|
||||
fn update_device_record(
|
||||
&self,
|
||||
config: &Config,
|
||||
refresh_token: &str,
|
||||
|
@ -353,7 +379,7 @@ impl FxAClient for Client {
|
|||
Ok(self.make_request(request)?.json()?)
|
||||
}
|
||||
|
||||
fn destroy_device(&self, config: &Config, refresh_token: &str, id: &str) -> Result<()> {
|
||||
fn destroy_device_record(&self, config: &Config, refresh_token: &str, id: &str) -> Result<()> {
|
||||
let body = json!({
|
||||
"id": id,
|
||||
});
|
||||
|
@ -367,7 +393,7 @@ impl FxAClient for Client {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn attached_clients(
|
||||
fn get_attached_clients(
|
||||
&self,
|
||||
config: &Config,
|
||||
session_token: &str,
|
||||
|
@ -378,7 +404,7 @@ impl FxAClient for Client {
|
|||
Ok(self.make_request(request)?.json()?)
|
||||
}
|
||||
|
||||
fn scoped_key_data(
|
||||
fn get_scoped_key_data(
|
||||
&self,
|
||||
config: &Config,
|
||||
session_token: &str,
|
||||
|
@ -528,8 +554,8 @@ pub struct AuthorizationRequestParameters {
|
|||
pub keys_jwe: Option<String>,
|
||||
}
|
||||
|
||||
// Keeping those functions out of the FxAClient trate becouse functions in the
|
||||
// FxAClient trate with a `test only` feature upsets the mockiato proc macro
|
||||
// Keeping those functions out of the FxAClient trait becouse functions in the
|
||||
// FxAClient trait with a `test only` feature upsets the mockiato proc macro
|
||||
// And it's okay since they are only used in tests. (if they were not test only
|
||||
// Mockiato would not complain)
|
||||
#[cfg(feature = "integration_test")]
|
||||
|
@ -708,6 +734,26 @@ pub struct PushSubscription {
|
|||
pub auth_key: String,
|
||||
}
|
||||
|
||||
impl From<crate::DevicePushSubscription> for PushSubscription {
|
||||
fn from(sub: crate::DevicePushSubscription) -> Self {
|
||||
PushSubscription {
|
||||
endpoint: sub.endpoint,
|
||||
public_key: sub.public_key,
|
||||
auth_key: sub.auth_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PushSubscription> for crate::DevicePushSubscription {
|
||||
fn from(sub: PushSubscription) -> Self {
|
||||
crate::DevicePushSubscription {
|
||||
endpoint: sub.endpoint,
|
||||
public_key: sub.public_key,
|
||||
auth_key: sub.auth_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// We use the double Option pattern in this struct.
|
||||
/// The outer option represents the existence of the field
|
||||
/// and the inner option its value or null.
|
||||
|
@ -791,7 +837,6 @@ impl<'a> DeviceUpdateRequestBuilder<'a> {
|
|||
self
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn device_type(mut self, device_type: &'a DeviceType) -> Self {
|
||||
self.device_type = Some(Some(device_type));
|
||||
self
|
||||
|
@ -928,6 +973,18 @@ pub struct ProfileResponse {
|
|||
pub avatar_default: bool,
|
||||
}
|
||||
|
||||
impl From<ProfileResponse> for crate::Profile {
|
||||
fn from(p: ProfileResponse) -> Self {
|
||||
crate::Profile {
|
||||
uid: p.uid,
|
||||
email: p.email,
|
||||
display_name: p.display_name,
|
||||
avatar: p.avatar,
|
||||
is_default_avatar: p.avatar_default,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ScopedKeyDataResponse {
|
||||
pub identifier: String,
|
|
@ -2,41 +2,13 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use crate::{error::*, scoped_keys::ScopedKey, scopes, FirefoxAccount};
|
||||
use ffi_support::IntoFfi;
|
||||
pub use crate::{FxAMigrationResult, MigrationState};
|
||||
|
||||
use super::{error::*, scoped_keys::ScopedKey, scopes, FirefoxAccount};
|
||||
use serde_derive::*;
|
||||
use std::convert::TryFrom;
|
||||
use std::time::Instant;
|
||||
|
||||
// Values to pass back to calling code over the FFI.
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Default)]
|
||||
pub struct FxAMigrationResult {
|
||||
pub total_duration: u128,
|
||||
}
|
||||
|
||||
pub enum MigrationState {
|
||||
// No in-flight migration.
|
||||
None,
|
||||
// An in-flight migration that will copy the sessionToken.
|
||||
CopySessionToken,
|
||||
// An in-flight migration that will re-use the sessionToken.
|
||||
ReuseSessionToken,
|
||||
}
|
||||
|
||||
unsafe impl IntoFfi for MigrationState {
|
||||
type Value = u8;
|
||||
fn ffi_default() -> u8 {
|
||||
0
|
||||
}
|
||||
fn into_ffi_value(self) -> u8 {
|
||||
match self {
|
||||
MigrationState::None => 0,
|
||||
MigrationState::CopySessionToken => 1,
|
||||
MigrationState::ReuseSessionToken => 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Migration-related data that we may need to serialize in the persisted account state.
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
|
@ -129,7 +101,8 @@ impl FirefoxAccount {
|
|||
self.state.in_flight_migration = None;
|
||||
|
||||
let metrics = FxAMigrationResult {
|
||||
total_duration: import_start.elapsed().as_millis(),
|
||||
// The foreign-language bindings are limited to an i64.
|
||||
total_duration: i64::try_from(import_start.elapsed().as_secs()).unwrap_or(i64::MAX),
|
||||
};
|
||||
|
||||
Ok(metrics)
|
||||
|
@ -150,7 +123,7 @@ impl FirefoxAccount {
|
|||
let migration_session_token = if migration_data.copy_session_token {
|
||||
let duplicate_session = self
|
||||
.client
|
||||
.duplicate_session(&self.state.config, &migration_data.session_token)?;
|
||||
.duplicate_session_token(&self.state.config, &migration_data.session_token)?;
|
||||
|
||||
duplicate_session.session_token
|
||||
} else {
|
||||
|
@ -164,7 +137,7 @@ impl FirefoxAccount {
|
|||
let k_sync = base64::encode_config(&k_sync, base64::URL_SAFE_NO_PAD);
|
||||
let k_xcs = hex::decode(&migration_data.k_xcs)?;
|
||||
let k_xcs = base64::encode_config(&k_xcs, base64::URL_SAFE_NO_PAD);
|
||||
let scoped_key_data = self.client.scoped_key_data(
|
||||
let scoped_key_data = self.client.get_scoped_key_data(
|
||||
&self.state.config,
|
||||
&migration_session_token,
|
||||
&self.state.config.client_id,
|
||||
|
@ -182,7 +155,7 @@ impl FirefoxAccount {
|
|||
};
|
||||
|
||||
// Trade our session token for a refresh token.
|
||||
let oauth_response = self.client.refresh_token_with_session_token(
|
||||
let oauth_response = self.client.create_refresh_token_using_session_token(
|
||||
&self.state.config,
|
||||
&migration_session_token,
|
||||
&[scopes::PROFILE, scopes::OLD_SYNC],
|
||||
|
@ -203,7 +176,7 @@ impl FirefoxAccount {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{http_client::*, Config};
|
||||
use crate::internal::{http_client::*, Config};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
@ -223,7 +196,9 @@ mod tests {
|
|||
// Initial attempt fails with a server-side failure, which we can retry.
|
||||
let mut client = FxAClientMock::new();
|
||||
client
|
||||
.expect_duplicate_session(mockiato::Argument::any, |arg| arg.partial_eq("session"))
|
||||
.expect_duplicate_session_token(mockiato::Argument::any, |arg| {
|
||||
arg.partial_eq("session")
|
||||
})
|
||||
.returns_once(Err(ErrorKind::RemoteError {
|
||||
code: 500,
|
||||
errno: 999,
|
||||
|
@ -250,7 +225,9 @@ mod tests {
|
|||
// It makes a lot of network requests, so we have a lot to mock!
|
||||
let mut client = FxAClientMock::new();
|
||||
client
|
||||
.expect_duplicate_session(mockiato::Argument::any, |arg| arg.partial_eq("session"))
|
||||
.expect_duplicate_session_token(mockiato::Argument::any, |arg| {
|
||||
arg.partial_eq("session")
|
||||
})
|
||||
.returns_once(Ok(DuplicateTokenResponse {
|
||||
uid: "userid".to_string(),
|
||||
session_token: "dup_session".to_string(),
|
||||
|
@ -267,7 +244,7 @@ mod tests {
|
|||
},
|
||||
);
|
||||
client
|
||||
.expect_scoped_key_data(
|
||||
.expect_get_scoped_key_data(
|
||||
mockiato::Argument::any,
|
||||
|arg| arg.partial_eq("dup_session"),
|
||||
|arg| arg.partial_eq("12345678"),
|
||||
|
@ -275,7 +252,7 @@ mod tests {
|
|||
)
|
||||
.returns_once(Ok(key_data));
|
||||
client
|
||||
.expect_refresh_token_with_session_token(
|
||||
.expect_create_refresh_token_using_session_token(
|
||||
mockiato::Argument::any,
|
||||
|arg| arg.partial_eq("dup_session"),
|
||||
|arg| arg.unordered_vec_eq([scopes::PROFILE, scopes::OLD_SYNC].to_vec()),
|
||||
|
@ -305,7 +282,9 @@ mod tests {
|
|||
|
||||
let mut client = FxAClientMock::new();
|
||||
client
|
||||
.expect_duplicate_session(mockiato::Argument::any, |arg| arg.partial_eq("session"))
|
||||
.expect_duplicate_session_token(mockiato::Argument::any, |arg| {
|
||||
arg.partial_eq("session")
|
||||
})
|
||||
.returns_once(Err(ErrorKind::RemoteError {
|
||||
code: 400,
|
||||
errno: 102,
|
||||
|
@ -345,7 +324,7 @@ mod tests {
|
|||
|
||||
let mut client = FxAClientMock::new();
|
||||
client
|
||||
.expect_scoped_key_data(
|
||||
.expect_get_scoped_key_data(
|
||||
mockiato::Argument::any,
|
||||
|arg| arg.partial_eq("session"),
|
||||
|arg| arg.partial_eq("12345678"),
|
||||
|
@ -377,7 +356,7 @@ mod tests {
|
|||
// to duplicate the sessionToken rather than reusing it).
|
||||
let mut client = FxAClientMock::new();
|
||||
client
|
||||
.expect_scoped_key_data(
|
||||
.expect_get_scoped_key_data(
|
||||
mockiato::Argument::any,
|
||||
|arg| arg.partial_eq("session"),
|
||||
|arg| arg.partial_eq("12345678"),
|
|
@ -0,0 +1,559 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
//! # Internal implementation details for the fxa_client crate.
|
||||
//!
|
||||
|
||||
// Currently public for use by example crates, but should be made private eventually.
|
||||
pub use self::{commands::IncomingDeviceCommand, config::Config};
|
||||
use self::{
|
||||
error::*,
|
||||
oauth::{AuthCircuitBreaker, OAuthFlow, OAUTH_WEBCHANNEL_REDIRECT},
|
||||
state_persistence::State,
|
||||
telemetry::FxaTelemetry,
|
||||
};
|
||||
use serde_derive::*;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
collections::{HashMap, HashSet},
|
||||
sync::Arc,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
#[cfg(feature = "integration_test")]
|
||||
pub mod auth;
|
||||
mod commands;
|
||||
pub mod config;
|
||||
pub mod device;
|
||||
pub mod error;
|
||||
mod http_client;
|
||||
mod migrator;
|
||||
mod oauth;
|
||||
mod profile;
|
||||
mod push;
|
||||
mod scoped_keys;
|
||||
mod scopes;
|
||||
mod send_tab;
|
||||
mod state_persistence;
|
||||
mod telemetry;
|
||||
mod util;
|
||||
|
||||
type FxAClient = dyn http_client::FxAClient + Sync + Send;
|
||||
|
||||
// FIXME: https://github.com/myelin-ai/mockiato/issues/106.
|
||||
#[cfg(test)]
|
||||
unsafe impl<'a> Send for http_client::FxAClientMock<'a> {}
|
||||
#[cfg(test)]
|
||||
unsafe impl<'a> Sync for http_client::FxAClientMock<'a> {}
|
||||
|
||||
// It this struct is modified, please check if the
|
||||
// `FirefoxAccount.start_over` function also needs
|
||||
// to be modified.
|
||||
pub struct FirefoxAccount {
|
||||
client: Arc<FxAClient>,
|
||||
state: State,
|
||||
flow_store: HashMap<String, OAuthFlow>,
|
||||
attached_clients_cache: Option<CachedResponse<Vec<http_client::GetAttachedClientResponse>>>,
|
||||
devices_cache: Option<CachedResponse<Vec<http_client::GetDeviceResponse>>>,
|
||||
auth_circuit_breaker: AuthCircuitBreaker,
|
||||
// 'telemetry' is only currently used by `&mut self` functions, but that's
|
||||
// not something we want to insist on going forward, so RefCell<> it.
|
||||
telemetry: RefCell<FxaTelemetry>,
|
||||
}
|
||||
|
||||
impl FirefoxAccount {
|
||||
fn from_state(state: State) -> Self {
|
||||
Self {
|
||||
client: Arc::new(http_client::Client::new()),
|
||||
state,
|
||||
flow_store: HashMap::new(),
|
||||
attached_clients_cache: None,
|
||||
devices_cache: None,
|
||||
auth_circuit_breaker: Default::default(),
|
||||
telemetry: RefCell::new(FxaTelemetry::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `FirefoxAccount` instance using a `Config`.
|
||||
///
|
||||
/// **💾 This method alters the persisted account state.**
|
||||
pub fn with_config(config: Config) -> Self {
|
||||
Self::from_state(State {
|
||||
config,
|
||||
refresh_token: None,
|
||||
scoped_keys: HashMap::new(),
|
||||
last_handled_command: None,
|
||||
commands_data: HashMap::new(),
|
||||
device_capabilities: HashSet::new(),
|
||||
session_token: None,
|
||||
current_device_id: None,
|
||||
last_seen_profile: None,
|
||||
access_token_cache: HashMap::new(),
|
||||
in_flight_migration: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new `FirefoxAccount` instance.
|
||||
///
|
||||
/// * `content_url` - The Firefox Account content server URL.
|
||||
/// * `client_id` - The OAuth `client_id`.
|
||||
/// * `redirect_uri` - The OAuth `redirect_uri`.
|
||||
/// * `token_server_url_override` - Override the Token Server URL provided
|
||||
/// by the FxA's autoconfig endpoint.
|
||||
///
|
||||
/// **💾 This method alters the persisted account state.**
|
||||
pub fn new(
|
||||
content_url: &str,
|
||||
client_id: &str,
|
||||
redirect_uri: &str,
|
||||
token_server_url_override: Option<&str>,
|
||||
) -> Self {
|
||||
let mut config = Config::new(content_url, client_id, redirect_uri);
|
||||
if let Some(token_server_url_override) = token_server_url_override {
|
||||
config.override_token_server_url(token_server_url_override.as_ref());
|
||||
}
|
||||
Self::with_config(config)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn set_client(&mut self, client: Arc<FxAClient>) {
|
||||
self.client = client;
|
||||
}
|
||||
|
||||
/// Restore a `FirefoxAccount` instance from a serialized state
|
||||
/// created using `to_json`.
|
||||
pub fn from_json(data: &str) -> Result<Self> {
|
||||
let state = state_persistence::state_from_json(data)?;
|
||||
Ok(Self::from_state(state))
|
||||
}
|
||||
|
||||
/// Serialize a `FirefoxAccount` instance internal state
|
||||
/// to be restored later using `from_json`.
|
||||
pub fn to_json(&self) -> Result<String> {
|
||||
state_persistence::state_to_json(&self.state)
|
||||
}
|
||||
|
||||
/// Clear the attached clients and devices cache
|
||||
pub fn clear_devices_and_attached_clients_cache(&mut self) {
|
||||
self.attached_clients_cache = None;
|
||||
self.devices_cache = None;
|
||||
}
|
||||
|
||||
/// Clear the whole persisted/cached state of the account, but keep just
|
||||
/// enough information to eventually reconnect to the same user account later.
|
||||
pub fn start_over(&mut self) {
|
||||
self.state = self.state.start_over();
|
||||
self.flow_store.clear();
|
||||
self.clear_devices_and_attached_clients_cache();
|
||||
self.telemetry.replace(FxaTelemetry::new());
|
||||
}
|
||||
|
||||
/// Get the Sync Token Server endpoint URL.
|
||||
pub fn get_token_server_endpoint_url(&self) -> Result<String> {
|
||||
Ok(self.state.config.token_server_endpoint_url()?.into_string())
|
||||
}
|
||||
|
||||
/// Get the pairing URL to navigate to on the Auth side (typically
|
||||
/// a computer).
|
||||
pub fn get_pairing_authority_url(&self) -> Result<String> {
|
||||
// Special case for the production server, we use the shorter firefox.com/pair URL.
|
||||
if self.state.config.content_url()? == Url::parse(config::CONTENT_URL_RELEASE)? {
|
||||
return Ok("https://firefox.com/pair".to_owned());
|
||||
}
|
||||
// Similarly special case for the China server.
|
||||
if self.state.config.content_url()? == Url::parse(config::CONTENT_URL_CHINA)? {
|
||||
return Ok("https://firefox.com.cn/pair".to_owned());
|
||||
}
|
||||
Ok(self.state.config.pair_url()?.into_string())
|
||||
}
|
||||
|
||||
/// Get the "connection succeeded" page URL.
|
||||
/// It is typically used to redirect the user after
|
||||
/// having intercepted the OAuth login-flow state/code
|
||||
/// redirection.
|
||||
pub fn get_connection_success_url(&self) -> Result<String> {
|
||||
let mut url = self.state.config.connect_another_device_url()?;
|
||||
url.query_pairs_mut()
|
||||
.append_pair("showSuccessMessage", "true");
|
||||
Ok(url.into_string())
|
||||
}
|
||||
|
||||
/// Get the "manage account" page URL.
|
||||
/// It is typically used in the application's account status UI,
|
||||
/// to link the user out to a webpage where they can manage
|
||||
/// all the details of their account.
|
||||
///
|
||||
/// * `entrypoint` - Application-provided string identifying the UI touchpoint
|
||||
/// through which the page was accessed, for metrics purposes.
|
||||
pub fn get_manage_account_url(&mut self, entrypoint: &str) -> Result<String> {
|
||||
let mut url = self.state.config.settings_url()?;
|
||||
url.query_pairs_mut().append_pair("entrypoint", entrypoint);
|
||||
if self.state.config.redirect_uri == OAUTH_WEBCHANNEL_REDIRECT {
|
||||
url.query_pairs_mut()
|
||||
.append_pair("context", "oauth_webchannel_v1");
|
||||
}
|
||||
self.add_account_identifiers_to_url(url)
|
||||
}
|
||||
|
||||
/// Get the "manage devices" page URL.
|
||||
/// It is typically used in the application's account status UI,
|
||||
/// to link the user out to a webpage where they can manage
|
||||
/// the devices connected to their account.
|
||||
///
|
||||
/// * `entrypoint` - Application-provided string identifying the UI touchpoint
|
||||
/// through which the page was accessed, for metrics purposes.
|
||||
pub fn get_manage_devices_url(&mut self, entrypoint: &str) -> Result<String> {
|
||||
let mut url = self.state.config.settings_clients_url()?;
|
||||
url.query_pairs_mut().append_pair("entrypoint", entrypoint);
|
||||
self.add_account_identifiers_to_url(url)
|
||||
}
|
||||
|
||||
fn add_account_identifiers_to_url(&mut self, mut url: Url) -> Result<String> {
|
||||
let profile = self.get_profile(false)?;
|
||||
url.query_pairs_mut()
|
||||
.append_pair("uid", &profile.uid)
|
||||
.append_pair("email", &profile.email);
|
||||
Ok(url.into_string())
|
||||
}
|
||||
|
||||
fn get_refresh_token(&self) -> Result<&str> {
|
||||
match self.state.refresh_token {
|
||||
Some(ref token_info) => Ok(&token_info.token),
|
||||
None => Err(ErrorKind::NoRefreshToken.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Disconnect from the account and optionally destroy our device record. This will
|
||||
/// leave the account object in a state where it can eventually reconnect to the same user.
|
||||
/// This is a "best effort" infallible method: e.g. if the network is unreachable,
|
||||
/// the device could still be in the FxA devices manager.
|
||||
///
|
||||
/// **💾 This method alters the persisted account state.**
|
||||
pub fn disconnect(&mut self) {
|
||||
let current_device_result;
|
||||
{
|
||||
current_device_result = self.get_current_device();
|
||||
}
|
||||
|
||||
if let Some(ref refresh_token) = self.state.refresh_token {
|
||||
// Delete the current device (which deletes the refresh token), or
|
||||
// the refresh token directly if we don't have a device.
|
||||
let destroy_result = match current_device_result {
|
||||
// If we get an error trying to fetch our device record we'll at least
|
||||
// still try to delete the refresh token itself.
|
||||
Ok(Some(device)) => self.client.destroy_device_record(
|
||||
&self.state.config,
|
||||
&refresh_token.token,
|
||||
&device.id,
|
||||
),
|
||||
_ => self
|
||||
.client
|
||||
.destroy_refresh_token(&self.state.config, &refresh_token.token),
|
||||
};
|
||||
if let Err(e) = destroy_result {
|
||||
log::warn!("Error while destroying the device: {}", e);
|
||||
}
|
||||
}
|
||||
self.start_over();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub(crate) struct CachedResponse<T> {
|
||||
response: T,
|
||||
cached_at: u64,
|
||||
etag: String,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::internal::device::*;
|
||||
use crate::internal::http_client::FxAClientMock;
|
||||
use crate::internal::oauth::*;
|
||||
|
||||
#[test]
|
||||
fn test_fxa_is_send() {
|
||||
fn is_send<T: Send>() {}
|
||||
is_send::<FirefoxAccount>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serialize_deserialize() {
|
||||
let config = Config::stable_dev("12345678", "https://foo.bar");
|
||||
let fxa1 = FirefoxAccount::with_config(config);
|
||||
let fxa1_json = fxa1.to_json().unwrap();
|
||||
drop(fxa1);
|
||||
let fxa2 = FirefoxAccount::from_json(&fxa1_json).unwrap();
|
||||
let fxa2_json = fxa2.to_json().unwrap();
|
||||
assert_eq!(fxa1_json, fxa2_json);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_connection_success_url() {
|
||||
let config = Config::new("https://stable.dev.lcip.org", "12345678", "https://foo.bar");
|
||||
let fxa = FirefoxAccount::with_config(config);
|
||||
let url = fxa.get_connection_success_url().unwrap();
|
||||
assert_eq!(
|
||||
url,
|
||||
"https://stable.dev.lcip.org/connect_another_device?showSuccessMessage=true"
|
||||
.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_manage_account_url() {
|
||||
let config = Config::new("https://stable.dev.lcip.org", "12345678", "https://foo.bar");
|
||||
let mut fxa = FirefoxAccount::with_config(config);
|
||||
// No current user -> Error.
|
||||
match fxa.get_manage_account_url("test").unwrap_err().kind() {
|
||||
ErrorKind::NoCachedToken(_) => {}
|
||||
_ => panic!("error not NoCachedToken"),
|
||||
};
|
||||
// With current user -> expected Url.
|
||||
fxa.add_cached_profile("123", "test@example.com");
|
||||
let url = fxa.get_manage_account_url("test").unwrap();
|
||||
assert_eq!(
|
||||
url,
|
||||
"https://stable.dev.lcip.org/settings?entrypoint=test&uid=123&email=test%40example.com"
|
||||
.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_manage_account_url_with_webchannel_redirect() {
|
||||
let config = Config::new(
|
||||
"https://stable.dev.lcip.org",
|
||||
"12345678",
|
||||
OAUTH_WEBCHANNEL_REDIRECT,
|
||||
);
|
||||
let mut fxa = FirefoxAccount::with_config(config);
|
||||
fxa.add_cached_profile("123", "test@example.com");
|
||||
let url = fxa.get_manage_account_url("test").unwrap();
|
||||
assert_eq!(
|
||||
url,
|
||||
"https://stable.dev.lcip.org/settings?entrypoint=test&context=oauth_webchannel_v1&uid=123&email=test%40example.com"
|
||||
.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_manage_devices_url() {
|
||||
let config = Config::new("https://stable.dev.lcip.org", "12345678", "https://foo.bar");
|
||||
let mut fxa = FirefoxAccount::with_config(config);
|
||||
// No current user -> Error.
|
||||
match fxa.get_manage_devices_url("test").unwrap_err().kind() {
|
||||
ErrorKind::NoCachedToken(_) => {}
|
||||
_ => panic!("error not NoCachedToken"),
|
||||
};
|
||||
// With current user -> expected Url.
|
||||
fxa.add_cached_profile("123", "test@example.com");
|
||||
let url = fxa.get_manage_devices_url("test").unwrap();
|
||||
assert_eq!(
|
||||
url,
|
||||
"https://stable.dev.lcip.org/settings/clients?entrypoint=test&uid=123&email=test%40example.com"
|
||||
.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_disconnect_no_refresh_token() {
|
||||
let config = Config::new("https://stable.dev.lcip.org", "12345678", "https://foo.bar");
|
||||
let mut fxa = FirefoxAccount::with_config(config);
|
||||
|
||||
fxa.add_cached_token(
|
||||
"profile",
|
||||
AccessTokenInfo {
|
||||
scope: "profile".to_string(),
|
||||
token: "profiletok".to_string(),
|
||||
key: None,
|
||||
expires_at: u64::max_value(),
|
||||
},
|
||||
);
|
||||
|
||||
let client = FxAClientMock::new();
|
||||
fxa.set_client(Arc::new(client));
|
||||
|
||||
assert!(!fxa.state.access_token_cache.is_empty());
|
||||
fxa.disconnect();
|
||||
assert!(fxa.state.access_token_cache.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_disconnect_device() {
|
||||
let config = Config::stable_dev("12345678", "https://foo.bar");
|
||||
let mut fxa = FirefoxAccount::with_config(config);
|
||||
|
||||
fxa.state.refresh_token = Some(RefreshToken {
|
||||
token: "refreshtok".to_string(),
|
||||
scopes: HashSet::default(),
|
||||
});
|
||||
|
||||
let mut client = FxAClientMock::new();
|
||||
client
|
||||
.expect_get_devices(mockiato::Argument::any, |token| {
|
||||
token.partial_eq("refreshtok")
|
||||
})
|
||||
.times(1)
|
||||
.returns_once(Ok(vec![
|
||||
Device {
|
||||
common: http_client::DeviceResponseCommon {
|
||||
id: "1234a".to_owned(),
|
||||
display_name: "My Device".to_owned(),
|
||||
device_type: http_client::DeviceType::Mobile,
|
||||
push_subscription: None,
|
||||
available_commands: HashMap::default(),
|
||||
push_endpoint_expired: false,
|
||||
},
|
||||
is_current_device: true,
|
||||
location: http_client::DeviceLocation {
|
||||
city: None,
|
||||
country: None,
|
||||
state: None,
|
||||
state_code: None,
|
||||
},
|
||||
last_access_time: None,
|
||||
},
|
||||
Device {
|
||||
common: http_client::DeviceResponseCommon {
|
||||
id: "a4321".to_owned(),
|
||||
display_name: "My Other Device".to_owned(),
|
||||
device_type: http_client::DeviceType::Desktop,
|
||||
push_subscription: None,
|
||||
available_commands: HashMap::default(),
|
||||
push_endpoint_expired: false,
|
||||
},
|
||||
is_current_device: false,
|
||||
location: http_client::DeviceLocation {
|
||||
city: None,
|
||||
country: None,
|
||||
state: None,
|
||||
state_code: None,
|
||||
},
|
||||
last_access_time: None,
|
||||
},
|
||||
]));
|
||||
client
|
||||
.expect_destroy_device_record(
|
||||
mockiato::Argument::any,
|
||||
|token| token.partial_eq("refreshtok"),
|
||||
|device_id| device_id.partial_eq("1234a"),
|
||||
)
|
||||
.times(1)
|
||||
.returns_once(Ok(()));
|
||||
fxa.set_client(Arc::new(client));
|
||||
|
||||
assert!(fxa.state.refresh_token.is_some());
|
||||
fxa.disconnect();
|
||||
assert!(fxa.state.refresh_token.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_disconnect_no_device() {
|
||||
let config = Config::stable_dev("12345678", "https://foo.bar");
|
||||
let mut fxa = FirefoxAccount::with_config(config);
|
||||
|
||||
fxa.state.refresh_token = Some(RefreshToken {
|
||||
token: "refreshtok".to_string(),
|
||||
scopes: HashSet::default(),
|
||||
});
|
||||
|
||||
let mut client = FxAClientMock::new();
|
||||
client
|
||||
.expect_get_devices(mockiato::Argument::any, |token| {
|
||||
token.partial_eq("refreshtok")
|
||||
})
|
||||
.times(1)
|
||||
.returns_once(Ok(vec![Device {
|
||||
common: http_client::DeviceResponseCommon {
|
||||
id: "a4321".to_owned(),
|
||||
display_name: "My Other Device".to_owned(),
|
||||
device_type: http_client::DeviceType::Desktop,
|
||||
push_subscription: None,
|
||||
available_commands: HashMap::default(),
|
||||
push_endpoint_expired: false,
|
||||
},
|
||||
is_current_device: false,
|
||||
location: http_client::DeviceLocation {
|
||||
city: None,
|
||||
country: None,
|
||||
state: None,
|
||||
state_code: None,
|
||||
},
|
||||
last_access_time: None,
|
||||
}]));
|
||||
client
|
||||
.expect_destroy_refresh_token(mockiato::Argument::any, |token| {
|
||||
token.partial_eq("refreshtok")
|
||||
})
|
||||
.times(1)
|
||||
.returns_once(Ok(()));
|
||||
fxa.set_client(Arc::new(client));
|
||||
|
||||
assert!(fxa.state.refresh_token.is_some());
|
||||
fxa.disconnect();
|
||||
assert!(fxa.state.refresh_token.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_disconnect_network_errors() {
|
||||
let config = Config::stable_dev("12345678", "https://foo.bar");
|
||||
let mut fxa = FirefoxAccount::with_config(config);
|
||||
|
||||
fxa.state.refresh_token = Some(RefreshToken {
|
||||
token: "refreshtok".to_string(),
|
||||
scopes: HashSet::default(),
|
||||
});
|
||||
|
||||
let mut client = FxAClientMock::new();
|
||||
client
|
||||
.expect_get_devices(mockiato::Argument::any, |token| {
|
||||
token.partial_eq("refreshtok")
|
||||
})
|
||||
.times(1)
|
||||
.returns_once(Ok(vec![]));
|
||||
client
|
||||
.expect_destroy_refresh_token(mockiato::Argument::any, |token| {
|
||||
token.partial_eq("refreshtok")
|
||||
})
|
||||
.times(1)
|
||||
.returns_once(Err(ErrorKind::RemoteError {
|
||||
code: 500,
|
||||
errno: 101,
|
||||
error: "Did not work!".to_owned(),
|
||||
message: "Did not work!".to_owned(),
|
||||
info: "Did not work!".to_owned(),
|
||||
}
|
||||
.into()));
|
||||
fxa.set_client(Arc::new(client));
|
||||
|
||||
assert!(fxa.state.refresh_token.is_some());
|
||||
fxa.disconnect();
|
||||
assert!(fxa.state.refresh_token.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_pairing_authority_url() {
|
||||
let config = Config::new("https://foo.bar", "12345678", "https://foo.bar");
|
||||
let fxa = FirefoxAccount::with_config(config);
|
||||
assert_eq!(
|
||||
fxa.get_pairing_authority_url().unwrap().as_str(),
|
||||
"https://foo.bar/pair"
|
||||
);
|
||||
|
||||
let config = Config::release("12345678", "https://foo.bar");
|
||||
let fxa = FirefoxAccount::with_config(config);
|
||||
assert_eq!(
|
||||
fxa.get_pairing_authority_url().unwrap().as_str(),
|
||||
"https://firefox.com/pair"
|
||||
);
|
||||
|
||||
let config = Config::china("12345678", "https://foo.bar");
|
||||
let fxa = FirefoxAccount::with_config(config);
|
||||
assert_eq!(
|
||||
fxa.get_pairing_authority_url().unwrap().as_str(),
|
||||
"https://firefox.com.cn/pair"
|
||||
)
|
||||
}
|
||||
}
|
|
@ -3,18 +3,20 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
pub mod attached_clients;
|
||||
|
||||
use crate::{
|
||||
use super::{
|
||||
error::*,
|
||||
http_client::{AuthorizationRequestParameters, OAuthTokenResponse},
|
||||
http_client::{
|
||||
AuthorizationRequestParameters, IntrospectResponse as IntrospectInfo, OAuthTokenResponse,
|
||||
},
|
||||
scoped_keys::{ScopedKey, ScopedKeysFlow},
|
||||
util, FirefoxAccount,
|
||||
};
|
||||
pub use crate::{AuthorizationParameters, MetricsParams};
|
||||
use jwcrypto::{EncryptionAlgorithm, EncryptionParameters};
|
||||
use rate_limiter::RateLimiter;
|
||||
use rc_crypto::digest;
|
||||
use serde_derive::*;
|
||||
use std::convert::TryFrom;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
iter::FromIterator,
|
||||
|
@ -50,7 +52,7 @@ impl FirefoxAccount {
|
|||
let resp = match self.state.refresh_token {
|
||||
Some(ref refresh_token) => {
|
||||
if refresh_token.scopes.contains(scope) {
|
||||
self.client.access_token_with_refresh_token(
|
||||
self.client.create_access_token_using_refresh_token(
|
||||
&self.state.config,
|
||||
&refresh_token.token,
|
||||
ttl,
|
||||
|
@ -61,7 +63,7 @@ impl FirefoxAccount {
|
|||
}
|
||||
}
|
||||
None => match self.state.session_token {
|
||||
Some(ref session_token) => self.client.access_token_with_session_token(
|
||||
Some(ref session_token) => self.client.create_access_token_using_session_token(
|
||||
&self.state.config,
|
||||
&session_token,
|
||||
&[scope],
|
||||
|
@ -99,7 +101,7 @@ impl FirefoxAccount {
|
|||
Some(ref refresh_token) => {
|
||||
self.auth_circuit_breaker.check()?;
|
||||
self.client
|
||||
.oauth_introspect_refresh_token(&self.state.config, &refresh_token.token)?
|
||||
.check_refresh_token_status(&self.state.config, &refresh_token.token)?
|
||||
}
|
||||
None => return Err(ErrorKind::NoRefreshToken.into()),
|
||||
};
|
||||
|
@ -199,7 +201,7 @@ impl FirefoxAccount {
|
|||
|
||||
// Validate request to ensure that the client is actually allowed to request
|
||||
// the scopes they requested
|
||||
let allowed_scopes = self.client.scoped_key_data(
|
||||
let allowed_scopes = self.client.get_scoped_key_data(
|
||||
&self.state.config,
|
||||
&session_token,
|
||||
&auth_params.client_id,
|
||||
|
@ -250,17 +252,12 @@ impl FirefoxAccount {
|
|||
scope: auth_params.scope.join(" "),
|
||||
state: auth_params.state,
|
||||
access_type: auth_params.access_type,
|
||||
code_challenge: auth_params
|
||||
.pkce_params
|
||||
.as_ref()
|
||||
.map(|param| param.code_challenge.clone()),
|
||||
code_challenge_method: auth_params
|
||||
.pkce_params
|
||||
.map(|param| param.code_challenge_method),
|
||||
code_challenge: auth_params.code_challenge,
|
||||
code_challenge_method: auth_params.code_challenge_method,
|
||||
keys_jwe,
|
||||
};
|
||||
|
||||
let resp = self.client.authorization_code_using_session_token(
|
||||
let resp = self.client.create_authorization_code_using_session_token(
|
||||
&self.state.config,
|
||||
&session_token,
|
||||
auth_request_params,
|
||||
|
@ -317,9 +314,9 @@ impl FirefoxAccount {
|
|||
Some(oauth_flow) => oauth_flow,
|
||||
None => return Err(ErrorKind::UnknownOAuthState.into()),
|
||||
};
|
||||
let resp = self.client.refresh_token_with_code(
|
||||
let resp = self.client.create_refresh_token_using_authorization_code(
|
||||
&self.state.config,
|
||||
&code,
|
||||
code,
|
||||
&oauth_flow.code_verifier,
|
||||
)?;
|
||||
self.handle_oauth_response(resp, oauth_flow.scoped_keys_flow)
|
||||
|
@ -420,7 +417,7 @@ impl FirefoxAccount {
|
|||
.as_ref()
|
||||
.ok_or(ErrorKind::NoRefreshToken)?;
|
||||
let scopes: Vec<&str> = old_refresh_token.scopes.iter().map(AsRef::as_ref).collect();
|
||||
let resp = self.client.refresh_token_with_session_token(
|
||||
let resp = self.client.create_refresh_token_using_session_token(
|
||||
&self.state.config,
|
||||
&session_token,
|
||||
&scopes,
|
||||
|
@ -450,7 +447,7 @@ impl FirefoxAccount {
|
|||
|
||||
#[cfg(feature = "integration_test")]
|
||||
pub fn new_logged_in(
|
||||
config: crate::Config,
|
||||
config: super::Config,
|
||||
session_token: &str,
|
||||
scoped_keys: HashMap<String, ScopedKey>,
|
||||
) -> Self {
|
||||
|
@ -491,22 +488,6 @@ impl AuthCircuitBreaker {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AuthorizationPKCEParams {
|
||||
pub code_challenge: String,
|
||||
pub code_challenge_method: String,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AuthorizationParameters {
|
||||
pub client_id: String,
|
||||
pub scope: Vec<String>,
|
||||
pub state: String,
|
||||
pub access_type: String,
|
||||
pub pkce_params: Option<AuthorizationPKCEParams>,
|
||||
pub keys_jwk: Option<String>,
|
||||
}
|
||||
|
||||
impl TryFrom<Url> for AuthorizationParameters {
|
||||
type Error = Error;
|
||||
|
||||
|
@ -530,27 +511,18 @@ impl TryFrom<Url> for AuthorizationParameters {
|
|||
.ok_or(ErrorKind::MissingUrlParameter("access_type"))?;
|
||||
let code_challenge = query_map.get("code_challenge").cloned();
|
||||
let code_challenge_method = query_map.get("code_challenge_method").cloned();
|
||||
let pkce_params = match (code_challenge, code_challenge_method) {
|
||||
(Some(code_challenge), Some(code_challenge_method)) => Some(AuthorizationPKCEParams {
|
||||
code_challenge,
|
||||
code_challenge_method,
|
||||
}),
|
||||
_ => None,
|
||||
};
|
||||
let keys_jwk = query_map.get("keys_jwk").cloned();
|
||||
Ok(Self {
|
||||
client_id,
|
||||
scope: scope.split_whitespace().map(|s| s.to_string()).collect(),
|
||||
state,
|
||||
access_type,
|
||||
pkce_params,
|
||||
code_challenge,
|
||||
code_challenge_method,
|
||||
keys_jwk,
|
||||
})
|
||||
}
|
||||
}
|
||||
pub struct MetricsParams {
|
||||
pub parameters: std::collections::HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl MetricsParams {
|
||||
fn append_params_to_url(&self, url: &mut Url) {
|
||||
|
@ -590,6 +562,18 @@ pub struct AccessTokenInfo {
|
|||
pub expires_at: u64, // seconds since epoch
|
||||
}
|
||||
|
||||
impl TryFrom<AccessTokenInfo> for crate::AccessTokenInfo {
|
||||
type Error = Error;
|
||||
fn try_from(info: AccessTokenInfo) -> Result<Self> {
|
||||
Ok(crate::AccessTokenInfo {
|
||||
scope: info.scope,
|
||||
token: info.token,
|
||||
key: info.key.map(ScopedKey::into),
|
||||
expires_at: info.expires_at.try_into()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for AccessTokenInfo {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("AccessTokenInfo")
|
||||
|
@ -600,15 +584,16 @@ impl std::fmt::Debug for AccessTokenInfo {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||
pub struct IntrospectInfo {
|
||||
pub active: bool,
|
||||
impl From<IntrospectInfo> for crate::AuthorizationInfo {
|
||||
fn from(r: IntrospectInfo) -> Self {
|
||||
crate::AuthorizationInfo { active: r.active }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::{http_client::*, Config};
|
||||
use super::*;
|
||||
use crate::{http_client::*, Config};
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
@ -895,7 +880,7 @@ mod tests {
|
|||
|
||||
let mut client = FxAClientMock::new();
|
||||
client
|
||||
.expect_oauth_introspect_refresh_token(mockiato::Argument::any, |token| {
|
||||
.expect_check_refresh_token_status(mockiato::Argument::any, |token| {
|
||||
token.partial_eq("refresh_token")
|
||||
})
|
||||
.times(1)
|
||||
|
@ -921,31 +906,31 @@ mod tests {
|
|||
// This copy-pasta (equivalent to `.returns(..).times(5)`) is there
|
||||
// because `Error` is not cloneable :/
|
||||
client
|
||||
.expect_oauth_introspect_refresh_token(mockiato::Argument::any, |token| {
|
||||
.expect_check_refresh_token_status(mockiato::Argument::any, |token| {
|
||||
token.partial_eq("refresh_token")
|
||||
})
|
||||
.returns_once(Ok(IntrospectResponse { active: true }));
|
||||
client
|
||||
.expect_oauth_introspect_refresh_token(mockiato::Argument::any, |token| {
|
||||
.expect_check_refresh_token_status(mockiato::Argument::any, |token| {
|
||||
token.partial_eq("refresh_token")
|
||||
})
|
||||
.returns_once(Ok(IntrospectResponse { active: true }));
|
||||
client
|
||||
.expect_oauth_introspect_refresh_token(mockiato::Argument::any, |token| {
|
||||
.expect_check_refresh_token_status(mockiato::Argument::any, |token| {
|
||||
token.partial_eq("refresh_token")
|
||||
})
|
||||
.returns_once(Ok(IntrospectResponse { active: true }));
|
||||
client
|
||||
.expect_oauth_introspect_refresh_token(mockiato::Argument::any, |token| {
|
||||
.expect_check_refresh_token_status(mockiato::Argument::any, |token| {
|
||||
token.partial_eq("refresh_token")
|
||||
})
|
||||
.returns_once(Ok(IntrospectResponse { active: true }));
|
||||
client
|
||||
.expect_oauth_introspect_refresh_token(mockiato::Argument::any, |token| {
|
||||
.expect_check_refresh_token_status(mockiato::Argument::any, |token| {
|
||||
token.partial_eq("refresh_token")
|
||||
})
|
||||
.returns_once(Ok(IntrospectResponse { active: true }));
|
||||
client.expect_oauth_introspect_refresh_token_calls_in_order();
|
||||
client.expect_check_refresh_token_status_calls_in_order();
|
||||
fxa.set_client(Arc::new(client));
|
||||
|
||||
for _ in 0..5 {
|
||||
|
@ -957,7 +942,7 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
use crate::scopes;
|
||||
use crate::internal::scopes;
|
||||
|
||||
#[test]
|
||||
fn test_auth_code_pair_valid_not_allowed_scope() {
|
||||
|
@ -972,7 +957,7 @@ mod tests {
|
|||
.chain(not_allowed_scope.chars())
|
||||
.collect::<String>();
|
||||
client
|
||||
.expect_scoped_key_data(
|
||||
.expect_get_scoped_key_data(
|
||||
mockiato::Argument::any,
|
||||
|arg| arg.partial_eq("session"),
|
||||
|arg| arg.partial_eq("12345678"),
|
||||
|
@ -992,7 +977,8 @@ mod tests {
|
|||
scope: vec![scopes::OLD_SYNC.to_string(), not_allowed_scope.to_string()],
|
||||
state: "somestate".to_string(),
|
||||
access_type: "offline".to_string(),
|
||||
pkce_params: None,
|
||||
code_challenge: None,
|
||||
code_challenge_method: None,
|
||||
keys_jwk: None,
|
||||
};
|
||||
let res = fxa.authorize_code_using_session_token(auth_params);
|
||||
|
@ -1035,7 +1021,7 @@ mod tests {
|
|||
},
|
||||
);
|
||||
client
|
||||
.expect_scoped_key_data(
|
||||
.expect_get_scoped_key_data(
|
||||
mockiato::Argument::any,
|
||||
|arg| arg.partial_eq("session"),
|
||||
|arg| arg.partial_eq("12345678"),
|
||||
|
@ -1049,7 +1035,8 @@ mod tests {
|
|||
scope: vec![scopes::OLD_SYNC.to_string(), invalid_scope.to_string()],
|
||||
state: "somestate".to_string(),
|
||||
access_type: "offline".to_string(),
|
||||
pkce_params: None,
|
||||
code_challenge: None,
|
||||
code_challenge_method: None,
|
||||
keys_jwk: None,
|
||||
};
|
||||
let res = fxa.authorize_code_using_session_token(auth_params);
|
||||
|
@ -1079,7 +1066,7 @@ mod tests {
|
|||
},
|
||||
);
|
||||
client
|
||||
.expect_scoped_key_data(
|
||||
.expect_get_scoped_key_data(
|
||||
mockiato::Argument::any,
|
||||
|arg| arg.partial_eq("session"),
|
||||
|arg| arg.partial_eq("12345678"),
|
||||
|
@ -1092,7 +1079,8 @@ mod tests {
|
|||
scope: vec![scopes::OLD_SYNC.to_string()],
|
||||
state: "somestate".to_string(),
|
||||
access_type: "offline".to_string(),
|
||||
pkce_params: None,
|
||||
code_challenge: None,
|
||||
code_challenge_method: None,
|
||||
keys_jwk: Some("IAmAVerySecretKeysJWkInBase64".to_string()),
|
||||
};
|
||||
let res = fxa.authorize_code_using_session_token(auth_params);
|
|
@ -2,8 +2,10 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
pub use crate::http_client::GetAttachedClientResponse as AttachedClient;
|
||||
use crate::{error::*, util, CachedResponse, FirefoxAccount};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
|
||||
pub use super::super::http_client::GetAttachedClientResponse as AttachedClient;
|
||||
use super::super::{error::*, util, CachedResponse, FirefoxAccount};
|
||||
|
||||
// An attached clients response is considered fresh for `ATTACHED_CLIENTS_FRESHNESS_THRESHOLD` ms.
|
||||
const ATTACHED_CLIENTS_FRESHNESS_THRESHOLD: u64 = 60_000; // 1 minute
|
||||
|
@ -19,7 +21,7 @@ impl FirefoxAccount {
|
|||
let session_token = self.get_session_token()?;
|
||||
let response = self
|
||||
.client
|
||||
.attached_clients(&self.state.config, &session_token)?;
|
||||
.get_attached_clients(&self.state.config, &session_token)?;
|
||||
|
||||
self.attached_clients_cache = Some(CachedResponse {
|
||||
response: response.clone(),
|
||||
|
@ -31,10 +33,26 @@ impl FirefoxAccount {
|
|||
}
|
||||
}
|
||||
|
||||
impl TryFrom<AttachedClient> for crate::AttachedClient {
|
||||
type Error = Error;
|
||||
fn try_from(c: AttachedClient) -> Result<Self> {
|
||||
Ok(crate::AttachedClient {
|
||||
client_id: c.client_id,
|
||||
device_id: c.device_id,
|
||||
device_type: c.device_type.map(Into::into),
|
||||
is_current_session: c.is_current_session,
|
||||
name: c.name,
|
||||
created_time: c.created_time.map(TryInto::try_into).transpose()?,
|
||||
last_access_time: c.last_access_time.map(TryInto::try_into).transpose()?,
|
||||
scope: c.scope,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
use crate::internal::{
|
||||
config::Config,
|
||||
http_client::{DeviceType, FxAClientMock},
|
||||
};
|
||||
|
@ -48,7 +66,7 @@ mod tests {
|
|||
|
||||
let mut client = FxAClientMock::new();
|
||||
client
|
||||
.expect_attached_clients(mockiato::Argument::any, |arg| arg.partial_eq("session"))
|
||||
.expect_get_attached_clients(mockiato::Argument::any, |arg| arg.partial_eq("session"))
|
||||
.times(1)
|
||||
.returns_once(Ok(vec![AttachedClient {
|
||||
client_id: Some("12345678".into()),
|
||||
|
@ -92,7 +110,7 @@ mod tests {
|
|||
|
||||
let mut client = FxAClientMock::new();
|
||||
client
|
||||
.expect_attached_clients(mockiato::Argument::any, |arg| arg.partial_eq("session"))
|
||||
.expect_get_attached_clients(mockiato::Argument::any, |arg| arg.partial_eq("session"))
|
||||
.times(1)
|
||||
.returns_once(Err(ErrorKind::RemoteError {
|
||||
code: 500,
|
|
@ -2,8 +2,8 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
pub use crate::http_client::ProfileResponse as Profile;
|
||||
use crate::{error::*, scopes, util, CachedResponse, FirefoxAccount};
|
||||
pub use super::http_client::ProfileResponse as Profile;
|
||||
use super::{error::*, scopes, util, CachedResponse, FirefoxAccount};
|
||||
|
||||
// A cached profile response is considered fresh for `PROFILE_FRESHNESS_THRESHOLD` ms.
|
||||
const PROFILE_FRESHNESS_THRESHOLD: u64 = 120_000; // 2 minutes
|
||||
|
@ -47,7 +47,7 @@ impl FirefoxAccount {
|
|||
let profile_access_token = self.get_access_token(scopes::PROFILE, None)?.token;
|
||||
match self
|
||||
.client
|
||||
.profile(&self.state.config, &profile_access_token, etag)?
|
||||
.get_profile(&self.state.config, &profile_access_token, etag)?
|
||||
{
|
||||
Some(response_and_etag) => {
|
||||
if let Some(etag) = response_and_etag.etag {
|
||||
|
@ -83,7 +83,7 @@ impl FirefoxAccount {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
use crate::internal::{
|
||||
http_client::*,
|
||||
oauth::{AccessTokenInfo, RefreshToken},
|
||||
Config,
|
||||
|
@ -123,7 +123,7 @@ mod tests {
|
|||
|
||||
let mut client = FxAClientMock::new();
|
||||
client
|
||||
.expect_profile(
|
||||
.expect_get_profile(
|
||||
mockiato::Argument::any,
|
||||
|token| token.partial_eq("profiletok"),
|
||||
mockiato::Argument::any,
|
||||
|
@ -169,7 +169,7 @@ mod tests {
|
|||
let mut client = FxAClientMock::new();
|
||||
// First call to profile() we fail with 401.
|
||||
client
|
||||
.expect_profile(
|
||||
.expect_get_profile(
|
||||
mockiato::Argument::any,
|
||||
|token| token.partial_eq("bad_access_token"),
|
||||
mockiato::Argument::any,
|
||||
|
@ -184,7 +184,7 @@ mod tests {
|
|||
}.into()));
|
||||
// Then we'll try to get a new access token.
|
||||
client
|
||||
.expect_access_token_with_refresh_token(
|
||||
.expect_create_access_token_using_refresh_token(
|
||||
mockiato::Argument::any,
|
||||
|token| token.partial_eq("refreshtok"),
|
||||
mockiato::Argument::any,
|
||||
|
@ -201,7 +201,7 @@ mod tests {
|
|||
}));
|
||||
// Then hooray it works!
|
||||
client
|
||||
.expect_profile(
|
||||
.expect_get_profile(
|
||||
mockiato::Argument::any,
|
||||
|token| token.partial_eq("good_profile_token"),
|
||||
mockiato::Argument::any,
|
|
@ -2,8 +2,11 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use crate::device::CommandFetchReason;
|
||||
use crate::{error::*, AccountEvent, FirefoxAccount};
|
||||
use std::convert::TryInto;
|
||||
|
||||
use super::device::CommandFetchReason;
|
||||
use super::{error::*, FirefoxAccount};
|
||||
use crate::AccountEvent;
|
||||
use serde_derive::Deserialize;
|
||||
|
||||
impl FirefoxAccount {
|
||||
|
@ -33,15 +36,19 @@ impl FirefoxAccount {
|
|||
match payload {
|
||||
PushPayload::CommandReceived(CommandReceivedPushPayload { index, .. }) => {
|
||||
if cfg!(target_os = "ios") {
|
||||
self.ios_fetch_device_command(index)
|
||||
.map(|cmd| vec![AccountEvent::IncomingDeviceCommand(Box::new(cmd))])
|
||||
let cmd = self.ios_fetch_device_command(index)?;
|
||||
Ok(vec![AccountEvent::CommandReceived {
|
||||
command: cmd.try_into()?,
|
||||
}])
|
||||
} else {
|
||||
self.poll_device_commands(CommandFetchReason::Push(index))
|
||||
.map(|cmds| {
|
||||
cmds.into_iter()
|
||||
.map(|cmd| AccountEvent::IncomingDeviceCommand(Box::new(cmd)))
|
||||
.collect()
|
||||
let cmds = self.poll_device_commands(CommandFetchReason::Push(index))?;
|
||||
cmds.into_iter()
|
||||
.map(|command| {
|
||||
Ok(AccountEvent::CommandReceived {
|
||||
command: command.try_into()?,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
PushPayload::ProfileUpdated => {
|
||||
|
@ -146,9 +153,11 @@ pub struct AccountDestroyedPushPayload {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::http_client::FxAClientMock;
|
||||
use crate::http_client::IntrospectResponse;
|
||||
use crate::CachedResponse;
|
||||
use crate::internal::http_client::FxAClientMock;
|
||||
use crate::internal::http_client::IntrospectResponse;
|
||||
use crate::internal::oauth::RefreshToken;
|
||||
use crate::internal::CachedResponse;
|
||||
use crate::internal::Config;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[test]
|
||||
|
@ -159,8 +168,10 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_push_profile_updated() {
|
||||
let mut fxa =
|
||||
FirefoxAccount::with_config(crate::Config::stable_dev("12345678", "https://foo.bar"));
|
||||
let mut fxa = FirefoxAccount::with_config(crate::internal::Config::stable_dev(
|
||||
"12345678",
|
||||
"https://foo.bar",
|
||||
));
|
||||
fxa.add_cached_profile("123", "test@example.com");
|
||||
let json = "{\"version\":1,\"command\":\"fxaccounts:profile_updated\"}";
|
||||
let events = fxa.handle_push_message(json).unwrap();
|
||||
|
@ -175,9 +186,9 @@ mod tests {
|
|||
#[test]
|
||||
fn test_push_device_disconnected_local() {
|
||||
let mut fxa =
|
||||
FirefoxAccount::with_config(crate::Config::stable_dev("12345678", "https://foo.bar"));
|
||||
FirefoxAccount::with_config(Config::stable_dev("12345678", "https://foo.bar"));
|
||||
let refresh_token_scopes = std::collections::HashSet::new();
|
||||
fxa.state.refresh_token = Some(crate::oauth::RefreshToken {
|
||||
fxa.state.refresh_token = Some(crate::internal::oauth::RefreshToken {
|
||||
token: "refresh_token".to_owned(),
|
||||
scopes: refresh_token_scopes,
|
||||
});
|
||||
|
@ -201,17 +212,17 @@ mod tests {
|
|||
#[test]
|
||||
fn test_push_password_reset() {
|
||||
let mut fxa =
|
||||
FirefoxAccount::with_config(crate::Config::stable_dev("12345678", "https://foo.bar"));
|
||||
FirefoxAccount::with_config(Config::stable_dev("12345678", "https://foo.bar"));
|
||||
let mut client = FxAClientMock::new();
|
||||
client
|
||||
.expect_oauth_introspect_refresh_token(mockiato::Argument::any, |token| {
|
||||
.expect_check_refresh_token_status(mockiato::Argument::any, |token| {
|
||||
token.partial_eq("refresh_token")
|
||||
})
|
||||
.times(1)
|
||||
.returns_once(Ok(IntrospectResponse { active: true }));
|
||||
fxa.set_client(Arc::new(client));
|
||||
let refresh_token_scopes = std::collections::HashSet::new();
|
||||
fxa.state.refresh_token = Some(crate::oauth::RefreshToken {
|
||||
fxa.state.refresh_token = Some(RefreshToken {
|
||||
token: "refresh_token".to_owned(),
|
||||
scopes: refresh_token_scopes,
|
||||
});
|
||||
|
@ -229,8 +240,10 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_push_device_disconnected_remote() {
|
||||
let mut fxa =
|
||||
FirefoxAccount::with_config(crate::Config::stable_dev("12345678", "https://foo.bar"));
|
||||
let mut fxa = FirefoxAccount::with_config(crate::internal::Config::stable_dev(
|
||||
"12345678",
|
||||
"https://foo.bar",
|
||||
));
|
||||
let json = "{\"version\":1,\"command\":\"fxaccounts:device_disconnected\",\"data\":{\"id\":\"remote_id\"}}";
|
||||
let events = fxa.handle_push_message(json).unwrap();
|
||||
assert_eq!(events.len(), 1);
|
||||
|
@ -249,7 +262,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_handle_push_message_ignores_unknown_command() {
|
||||
let mut fxa =
|
||||
FirefoxAccount::with_config(crate::Config::stable_dev("12345678", "https://foo.bar"));
|
||||
FirefoxAccount::with_config(Config::stable_dev("12345678", "https://foo.bar"));
|
||||
let json = "{\"version\":1,\"command\":\"huh\"}";
|
||||
let events = fxa.handle_push_message(json).unwrap();
|
||||
assert!(events.is_empty());
|
||||
|
@ -258,7 +271,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_handle_push_message_ignores_unknown_command_with_data() {
|
||||
let mut fxa =
|
||||
FirefoxAccount::with_config(crate::Config::stable_dev("12345678", "https://foo.bar"));
|
||||
FirefoxAccount::with_config(Config::stable_dev("12345678", "https://foo.bar"));
|
||||
let json = "{\"version\":1,\"command\":\"huh\",\"data\":{\"value\":42}}";
|
||||
let events = fxa.handle_push_message(json).unwrap();
|
||||
assert!(events.is_empty());
|
||||
|
@ -267,7 +280,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_handle_push_message_errors_on_garbage_data() {
|
||||
let mut fxa =
|
||||
FirefoxAccount::with_config(crate::Config::stable_dev("12345678", "https://foo.bar"));
|
||||
FirefoxAccount::with_config(Config::stable_dev("12345678", "https://foo.bar"));
|
||||
let json = "{\"wtf\":\"bbq\"}";
|
||||
fxa.handle_push_message(json).unwrap_err();
|
||||
}
|
|
@ -2,10 +2,11 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use crate::{error::*, FirefoxAccount};
|
||||
use jwcrypto::{self, DecryptionParameters, Jwk};
|
||||
use rc_crypto::{agreement, agreement::EphemeralKeyPair};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
use super::{error::*, FirefoxAccount};
|
||||
pub use crate::ScopedKey;
|
||||
|
||||
impl FirefoxAccount {
|
||||
pub(crate) fn get_scoped_key(&self, scope: &str) -> Result<&ScopedKey> {
|
||||
|
@ -16,15 +17,6 @@ impl FirefoxAccount {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct ScopedKey {
|
||||
pub kty: String,
|
||||
pub scope: String,
|
||||
/// URL Safe Base 64 encoded key.
|
||||
pub k: String,
|
||||
pub kid: String,
|
||||
}
|
||||
|
||||
impl ScopedKey {
|
||||
pub fn key_bytes(&self) -> Result<Vec<u8>> {
|
||||
Ok(base64::decode_config(&self.k, base64::URL_SAFE_NO_PAD)?)
|
|
@ -2,14 +2,17 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
pub use crate::commands::send_tab::{SendTabPayload, TabHistoryEntry};
|
||||
use crate::{
|
||||
commands::send_tab::{
|
||||
self, EncryptedSendTabPayload, PrivateSendTabKeys, PublicSendTabKeys, SendTabKeysPayload,
|
||||
use super::{
|
||||
commands::{
|
||||
send_tab::{
|
||||
self, EncryptedSendTabPayload, PrivateSendTabKeys, PublicSendTabKeys,
|
||||
SendTabKeysPayload, SendTabPayload,
|
||||
},
|
||||
IncomingDeviceCommand,
|
||||
},
|
||||
error::*,
|
||||
http_client::GetDeviceResponse,
|
||||
scopes, telemetry, FirefoxAccount, IncomingDeviceCommand,
|
||||
scopes, telemetry, FirefoxAccount,
|
||||
};
|
||||
|
||||
impl FirefoxAccount {
|
||||
|
@ -42,8 +45,13 @@ impl FirefoxAccount {
|
|||
/// telemetry for these cases.
|
||||
/// This probably requires a new "Tab" struct with the title and url.
|
||||
/// android-components has SendToAllUseCase(), so this isn't just theoretical.
|
||||
/// See https://github.com/mozilla/application-services/issues/3402
|
||||
pub fn send_tab(&mut self, target_device_id: &str, title: &str, url: &str) -> Result<()> {
|
||||
/// See <https://github.com/mozilla/application-services/issues/3402>
|
||||
pub fn send_single_tab(
|
||||
&mut self,
|
||||
target_device_id: &str,
|
||||
title: &str,
|
||||
url: &str,
|
||||
) -> Result<()> {
|
||||
let devices = self.get_devices(false)?;
|
||||
let target = devices
|
||||
.iter()
|
|
@ -2,6 +2,8 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
//! Serialization of `FirefoxAccount` state to/from a JSON string.
|
||||
//!
|
||||
//! This module implements the ability to serialize a `FirefoxAccount` struct to and from
|
||||
//! a JSON string. The idea is that calling code will use this to persist the account state
|
||||
//! to storage.
|
||||
|
@ -28,7 +30,7 @@
|
|||
use serde_derive::*;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use crate::{
|
||||
use super::{
|
||||
config::Config,
|
||||
device::Capability as DeviceCapability,
|
||||
migrator::MigrationData,
|
||||
|
@ -38,15 +40,19 @@ use crate::{
|
|||
CachedResponse, Result,
|
||||
};
|
||||
|
||||
// These are public API for working with the persisted state.
|
||||
// These are the public API for working with the persisted state.
|
||||
|
||||
pub(crate) type State = StateV2;
|
||||
|
||||
/// Parse a `State` from a JSON string, performing migrations if necessary.
|
||||
///
|
||||
pub(crate) fn state_from_json(data: &str) -> Result<State> {
|
||||
let stored_state: PersistedState = serde_json::from_str(data)?;
|
||||
upgrade_state(stored_state)
|
||||
}
|
||||
|
||||
/// Serialize a `State` to a JSON string.
|
||||
///
|
||||
pub(crate) fn state_to_json(state: &State) -> Result<String> {
|
||||
let state = PersistedState::V2(state.clone());
|
||||
serde_json::to_string(&state).map_err(Into::into)
|
||||
|
@ -59,9 +65,9 @@ fn upgrade_state(in_state: PersistedState) -> Result<State> {
|
|||
}
|
||||
}
|
||||
|
||||
// `PersistedState` is a tagged container for one of the state versions.
|
||||
// Serde picks the right `StructVX` to deserialized based on the schema_version tag.
|
||||
|
||||
/// `PersistedState` is a tagged container for one of the state versions.
|
||||
/// Serde picks the right `StructVX` to deserialized based on the schema_version tag.
|
||||
///
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(tag = "schema_version")]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
|
@ -71,18 +77,18 @@ enum PersistedState {
|
|||
V2(StateV2),
|
||||
}
|
||||
|
||||
// `StateV2` is the current state schema. It and its fields all need to be public
|
||||
// so that they can be used directly elsewhere in the crate.
|
||||
//
|
||||
// If you want to modify what gets stored in the state, consider the following:
|
||||
//
|
||||
// * Is the change backwards-compatible with previously-serialized data?
|
||||
// If so then you'll need to tell serde how to fill in a suitable default.
|
||||
// If not then you'll need to make a new `StateV3` and implement an explicit migration.
|
||||
//
|
||||
// * Does the new field need to be modified when the user disconnects from the account?
|
||||
// If so then you'll need to update `StateV2.start_over` function.
|
||||
|
||||
/// `StateV2` is the current state schema. It and its fields all need to be public
|
||||
/// so that they can be used directly elsewhere in the crate.
|
||||
///
|
||||
/// If you want to modify what gets stored in the state, consider the following:
|
||||
///
|
||||
/// * Is the change backwards-compatible with previously-serialized data?
|
||||
/// If so then you'll need to tell serde how to fill in a suitable default.
|
||||
/// If not then you'll need to make a new `StateV3` and implement an explicit migration.
|
||||
///
|
||||
/// * Does the new field need to be modified when the user disconnects from the account?
|
||||
/// If so then you'll need to update `StateV2.start_over` function.
|
||||
///
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub(crate) struct StateV2 {
|
||||
pub(crate) config: Config,
|
||||
|
@ -105,8 +111,12 @@ pub(crate) struct StateV2 {
|
|||
}
|
||||
|
||||
impl StateV2 {
|
||||
/// Clear the whole persisted state of the account, but keep just enough
|
||||
/// information to eventually reconnect to the same user account later.
|
||||
/// Clear (almost all of) the persisted state of the account.
|
||||
///
|
||||
/// This method keep just enough information to be able to eventually reconnect
|
||||
/// to the same user account later. To completely forget the previously-signed-in
|
||||
/// user, simply discard the persisted data.
|
||||
///
|
||||
pub(crate) fn start_over(&self) -> StateV2 {
|
||||
StateV2 {
|
||||
config: self.config.clone(),
|
||||
|
@ -125,10 +135,11 @@ impl StateV2 {
|
|||
}
|
||||
}
|
||||
|
||||
// Migration from `StateV1`. There was a lot of changing of structs and renaming of fields,
|
||||
// but the key change is that we went from supporting multiple active refresh_tokens to
|
||||
// only supporting a single one.
|
||||
|
||||
/// Migration from `StateV1` to `StateV2`.
|
||||
/// There was a lot of changing of structs and renaming of fields,
|
||||
/// but the key change is that we went from supporting multiple active
|
||||
/// refresh_tokens to only supporting a single one.
|
||||
///
|
||||
impl From<StateV1> for Result<StateV2> {
|
||||
fn from(state: StateV1) -> Self {
|
||||
let mut all_refresh_tokens: Vec<V1AuthInfo> = vec![];
|
||||
|
@ -190,15 +201,15 @@ impl From<StateV1> for Result<StateV2> {
|
|||
}
|
||||
}
|
||||
|
||||
// `StateV1` was a previous state schema.
|
||||
//
|
||||
// The below is sufficient to read existing state data serialized in this form, but should not
|
||||
// be used to create new data using that schema, so it is deliberately private and deliberately
|
||||
// does not derive(Serialize).
|
||||
//
|
||||
// If you find yourself modifying this code, you're almost certainly creating a potential data-migration
|
||||
// problem and should reconsider.
|
||||
|
||||
/// `StateV1` was a previous state schema.
|
||||
///
|
||||
/// The below is sufficient to read existing state data serialized in this form, but should not
|
||||
/// be used to create new data using that schema, so it is deliberately private and deliberately
|
||||
/// does not derive(Serialize).
|
||||
///
|
||||
/// If you find yourself modifying this code, you're almost certainly creating a potential data-migration
|
||||
/// problem and should reconsider.
|
||||
///
|
||||
#[derive(Deserialize)]
|
||||
struct StateV1 {
|
||||
client_id: String,
|
|
@ -2,7 +2,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use crate::{error::*, FirefoxAccount};
|
||||
use super::{error::*, FirefoxAccount};
|
||||
use serde_derive::*;
|
||||
use sync_guid::Guid;
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use crate::error::*;
|
||||
use super::error::*;
|
||||
use rc_crypto::rand;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,208 +0,0 @@
|
|||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct Profile {
|
||||
#[prost(string, optional, tag="1")]
|
||||
pub uid: ::std::option::Option<std::string::String>,
|
||||
#[prost(string, optional, tag="2")]
|
||||
pub email: ::std::option::Option<std::string::String>,
|
||||
#[prost(string, optional, tag="3")]
|
||||
pub avatar: ::std::option::Option<std::string::String>,
|
||||
#[prost(bool, optional, tag="4")]
|
||||
pub avatar_default: ::std::option::Option<bool>,
|
||||
#[prost(string, optional, tag="5")]
|
||||
pub display_name: ::std::option::Option<std::string::String>,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct AccessTokenInfo {
|
||||
#[prost(string, required, tag="1")]
|
||||
pub scope: std::string::String,
|
||||
#[prost(string, required, tag="2")]
|
||||
pub token: std::string::String,
|
||||
#[prost(message, optional, tag="3")]
|
||||
pub key: ::std::option::Option<ScopedKey>,
|
||||
#[prost(uint64, required, tag="4")]
|
||||
pub expires_at: u64,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct IntrospectInfo {
|
||||
#[prost(bool, required, tag="1")]
|
||||
pub active: bool,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct ScopedKey {
|
||||
#[prost(string, required, tag="1")]
|
||||
pub kty: std::string::String,
|
||||
#[prost(string, required, tag="2")]
|
||||
pub scope: std::string::String,
|
||||
#[prost(string, required, tag="3")]
|
||||
pub k: std::string::String,
|
||||
#[prost(string, required, tag="4")]
|
||||
pub kid: std::string::String,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct Device {
|
||||
#[prost(string, required, tag="1")]
|
||||
pub id: std::string::String,
|
||||
#[prost(string, required, tag="2")]
|
||||
pub display_name: std::string::String,
|
||||
#[prost(enumeration="device::Type", required, tag="3")]
|
||||
pub r#type: i32,
|
||||
#[prost(message, optional, tag="4")]
|
||||
pub push_subscription: ::std::option::Option<device::PushSubscription>,
|
||||
#[prost(bool, required, tag="5")]
|
||||
pub push_endpoint_expired: bool,
|
||||
#[prost(bool, required, tag="6")]
|
||||
pub is_current_device: bool,
|
||||
#[prost(uint64, optional, tag="7")]
|
||||
pub last_access_time: ::std::option::Option<u64>,
|
||||
#[prost(enumeration="device::Capability", repeated, packed="false", tag="8")]
|
||||
pub capabilities: ::std::vec::Vec<i32>,
|
||||
}
|
||||
pub mod device {
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct PushSubscription {
|
||||
#[prost(string, required, tag="1")]
|
||||
pub endpoint: std::string::String,
|
||||
#[prost(string, required, tag="2")]
|
||||
pub public_key: std::string::String,
|
||||
#[prost(string, required, tag="3")]
|
||||
pub auth_key: std::string::String,
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
|
||||
#[repr(i32)]
|
||||
pub enum Capability {
|
||||
SendTab = 1,
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
|
||||
#[repr(i32)]
|
||||
pub enum Type {
|
||||
Desktop = 1,
|
||||
Mobile = 2,
|
||||
Tablet = 3,
|
||||
Vr = 4,
|
||||
Tv = 5,
|
||||
Unknown = 6,
|
||||
}
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct Devices {
|
||||
#[prost(message, repeated, tag="1")]
|
||||
pub devices: ::std::vec::Vec<Device>,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct Capabilities {
|
||||
#[prost(enumeration="device::Capability", repeated, packed="false", tag="1")]
|
||||
pub capability: ::std::vec::Vec<i32>,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct IncomingDeviceCommand {
|
||||
#[prost(enumeration="incoming_device_command::IncomingDeviceCommandType", required, tag="1")]
|
||||
pub r#type: i32,
|
||||
#[prost(oneof="incoming_device_command::Data", tags="2")]
|
||||
pub data: ::std::option::Option<incoming_device_command::Data>,
|
||||
}
|
||||
pub mod incoming_device_command {
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct SendTabData {
|
||||
#[prost(message, optional, tag="1")]
|
||||
pub from: ::std::option::Option<super::Device>,
|
||||
#[prost(message, repeated, tag="2")]
|
||||
pub entries: ::std::vec::Vec<send_tab_data::TabHistoryEntry>,
|
||||
}
|
||||
pub mod send_tab_data {
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct TabHistoryEntry {
|
||||
#[prost(string, required, tag="1")]
|
||||
pub title: std::string::String,
|
||||
#[prost(string, required, tag="2")]
|
||||
pub url: std::string::String,
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
|
||||
#[repr(i32)]
|
||||
pub enum IncomingDeviceCommandType {
|
||||
/// `data` set to `tab_received_data`.
|
||||
TabReceived = 1,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Oneof)]
|
||||
pub enum Data {
|
||||
#[prost(message, tag="2")]
|
||||
TabReceivedData(SendTabData),
|
||||
}
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct IncomingDeviceCommands {
|
||||
#[prost(message, repeated, tag="1")]
|
||||
pub commands: ::std::vec::Vec<IncomingDeviceCommand>,
|
||||
}
|
||||
/// This is basically an enum with associated values,
|
||||
/// but it's a bit harder to model in proto2.
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct AccountEvent {
|
||||
#[prost(enumeration="account_event::AccountEventType", required, tag="1")]
|
||||
pub r#type: i32,
|
||||
#[prost(oneof="account_event::Data", tags="2, 3, 4")]
|
||||
pub data: ::std::option::Option<account_event::Data>,
|
||||
}
|
||||
pub mod account_event {
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct DeviceDisconnectedData {
|
||||
#[prost(string, required, tag="1")]
|
||||
pub device_id: std::string::String,
|
||||
#[prost(bool, required, tag="2")]
|
||||
pub is_local_device: bool,
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
|
||||
#[repr(i32)]
|
||||
pub enum AccountEventType {
|
||||
/// `data` set to `device_command`.
|
||||
IncomingDeviceCommand = 1,
|
||||
ProfileUpdated = 2,
|
||||
/// `data` set to `device_connected_name`.
|
||||
DeviceConnected = 3,
|
||||
AccountAuthStateChanged = 4,
|
||||
/// `data` set to `device_disconnected_data`.
|
||||
DeviceDisconnected = 5,
|
||||
AccountDestroyed = 6,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Oneof)]
|
||||
pub enum Data {
|
||||
#[prost(message, tag="2")]
|
||||
DeviceCommand(super::IncomingDeviceCommand),
|
||||
#[prost(string, tag="3")]
|
||||
DeviceConnectedName(std::string::String),
|
||||
#[prost(message, tag="4")]
|
||||
DeviceDisconnectedData(DeviceDisconnectedData),
|
||||
}
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct AccountEvents {
|
||||
#[prost(message, repeated, tag="1")]
|
||||
pub events: ::std::vec::Vec<AccountEvent>,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct AuthorizationPkceParams {
|
||||
#[prost(string, required, tag="1")]
|
||||
pub code_challenge: std::string::String,
|
||||
#[prost(string, required, tag="2")]
|
||||
pub code_challenge_method: std::string::String,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct AuthorizationParams {
|
||||
#[prost(string, required, tag="1")]
|
||||
pub client_id: std::string::String,
|
||||
#[prost(string, required, tag="2")]
|
||||
pub scope: std::string::String,
|
||||
#[prost(string, required, tag="3")]
|
||||
pub state: std::string::String,
|
||||
#[prost(string, required, tag="4")]
|
||||
pub access_type: std::string::String,
|
||||
#[prost(message, optional, tag="5")]
|
||||
pub pkce_params: ::std::option::Option<AuthorizationPkceParams>,
|
||||
#[prost(string, optional, tag="6")]
|
||||
pub keys_jwk: ::std::option::Option<std::string::String>,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct MetricsParams {
|
||||
#[prost(map="string, string", tag="1")]
|
||||
pub parameters: ::std::collections::HashMap<std::string::String, std::string::String>,
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
[bindings.kotlin]
|
||||
package_name = "mozilla.appservices.fxaclient"
|
||||
cdylib_name = "megazord"
|
|
@ -2,19 +2,24 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// Utilities for command-line utilities which want to use fxa credentials.
|
||||
|
||||
use crate::prompt::prompt_string;
|
||||
use fxa_client::{error, AccessTokenInfo, Config, FirefoxAccount};
|
||||
use std::collections::HashMap;
|
||||
/// Utilities for command-line utilities which want to use fxa credentials.
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
convert::TryInto,
|
||||
fs,
|
||||
io::{Read, Write},
|
||||
};
|
||||
use sync15::{KeyBundle, Sync15StorageClientInit};
|
||||
use url::Url;
|
||||
|
||||
use anyhow::Result;
|
||||
use url::Url;
|
||||
|
||||
// This crate awkardly uses some internal implementation details of the fxa-client crate,
|
||||
// because we haven't worked on exposing those test-only features via UniFFI.
|
||||
use fxa_client::internal::{config::Config, error, FirefoxAccount};
|
||||
use fxa_client::AccessTokenInfo;
|
||||
use sync15::{KeyBundle, Sync15StorageClientInit};
|
||||
|
||||
use crate::prompt::prompt_string;
|
||||
|
||||
// Defaults - not clear they are the best option, but they are a currently
|
||||
// working option.
|
||||
|
@ -60,7 +65,11 @@ fn create_fxa_creds(path: &str, cfg: Config) -> Result<FirefoxAccount> {
|
|||
|
||||
acct.complete_oauth_flow(&query_params["code"], &query_params["state"])?;
|
||||
// Device registration.
|
||||
acct.initialize_device("CLI Device", fxa_client::device::Type::Desktop, &[])?;
|
||||
acct.initialize_device(
|
||||
"CLI Device",
|
||||
fxa_client::internal::device::Type::Desktop,
|
||||
&[],
|
||||
)?;
|
||||
let mut file = fs::File::create(path)?;
|
||||
write!(file, "{}", acct.to_json()?)?;
|
||||
file.flush()?;
|
||||
|
@ -82,7 +91,7 @@ fn get_account_and_token(
|
|||
let mut acct = load_or_create_fxa_creds(cred_file, config.clone())?;
|
||||
// `scope` could be a param, but I can't see it changing.
|
||||
match acct.get_access_token(SYNC_SCOPE, None) {
|
||||
Ok(t) => Ok((acct, t)),
|
||||
Ok(t) => Ok((acct, t.try_into()?)),
|
||||
Err(e) => {
|
||||
match e.kind() {
|
||||
// We can retry an auth error.
|
||||
|
@ -90,7 +99,7 @@ fn get_account_and_token(
|
|||
println!("Saw an auth error using stored credentials - recreating them...");
|
||||
acct = create_fxa_creds(cred_file, config)?;
|
||||
let token = acct.get_access_token(SYNC_SCOPE, None)?;
|
||||
Ok((acct, token))
|
||||
Ok((acct, token.try_into()?))
|
||||
}
|
||||
_ => Err(e.into()),
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
use cli_support::prompt::prompt_string;
|
||||
use dialoguer::Select;
|
||||
use fxa_client::{device, Config, FirefoxAccount, IncomingDeviceCommand};
|
||||
use fxa_client::internal::{device, Config, FirefoxAccount, IncomingDeviceCommand};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs,
|
||||
|
@ -147,7 +147,7 @@ fn main() -> Result<()> {
|
|||
let url: String = prompt_string("URL").unwrap();
|
||||
acct.lock()
|
||||
.unwrap()
|
||||
.send_tab(&target.id, &title, &url)
|
||||
.send_single_tab(&target.id, &title, &url)
|
||||
.unwrap();
|
||||
println!("Tab sent!");
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use cli_support::prompt::prompt_string;
|
||||
use fxa_client::{Config, FirefoxAccount};
|
||||
use fxa_client::internal::{Config, FirefoxAccount};
|
||||
use std::{thread, time};
|
||||
|
||||
static CLIENT_ID: &str = "3c49430b43dfba77";
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
use cli_support::prompt::prompt_string;
|
||||
use fxa_client::{Config, FirefoxAccount};
|
||||
use fxa_client::internal::{Config, FirefoxAccount};
|
||||
use std::collections::HashMap;
|
||||
use url::Url;
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ license = "MPL-2.0"
|
|||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
fxaclient_ffi = { path = "../../components/fxa-client/ffi" }
|
||||
fxa-client = { path = "../../components/fxa-client" }
|
||||
logins_ffi = { path = "../../components/logins/ffi" }
|
||||
places-ffi = { path = "../../components/places/ffi" }
|
||||
push-ffi = { path = "../../components/push/ffi" }
|
||||
|
|
|
@ -37,7 +37,8 @@ The following text applies to code linked from these dependencies:
|
|||
[jexl-parser](https://github.com/mozilla/jexl-rs),
|
||||
[uniffi](https://github.com/mozilla/uniffi-rs),
|
||||
[uniffi_bindgen](https://github.com/mozilla/uniffi-rs),
|
||||
[uniffi_build](https://github.com/mozilla/uniffi-rs)
|
||||
[uniffi_build](https://github.com/mozilla/uniffi-rs),
|
||||
[uniffi_macros](https://github.com/mozilla/uniffi-rs)
|
||||
|
||||
```
|
||||
Mozilla Public License Version 2.0
|
||||
|
@ -439,6 +440,7 @@ The following text applies to code linked from these dependencies:
|
|||
[fallible-streaming-iterator](https://github.com/sfackler/fallible-streaming-iterator),
|
||||
[ffi-support](https://github.com/mozilla/application-services),
|
||||
[getrandom](https://github.com/rust-random/getrandom),
|
||||
[glob](https://github.com/rust-lang/glob),
|
||||
[hashbrown](https://github.com/rust-lang/hashbrown),
|
||||
[hashlink](https://github.com/kyren/hashlink),
|
||||
[heck](https://github.com/withoutboats/heck),
|
||||
|
|
|
@ -40,6 +40,10 @@ the details of which are reproduced below.
|
|||
<name>Mozilla Public License 2.0: uniffi_build</name>
|
||||
<url>https://github.com/mozilla/uniffi-rs/blob/main/LICENSE</url>
|
||||
</license>
|
||||
<license>
|
||||
<name>Mozilla Public License 2.0: uniffi_macros</name>
|
||||
<url>https://github.com/mozilla/uniffi-rs/blob/main/LICENSE</url>
|
||||
</license>
|
||||
<license>
|
||||
<name>Apache License 2.0: ahash</name>
|
||||
<url>https://github.com/tkaitchuck/ahash/blob/master/LICENSE-APACHE</url>
|
||||
|
@ -124,6 +128,10 @@ the details of which are reproduced below.
|
|||
<name>Apache License 2.0: getrandom</name>
|
||||
<url>https://github.com/rust-random/getrandom/blob/master/LICENSE-APACHE</url>
|
||||
</license>
|
||||
<license>
|
||||
<name>Apache License 2.0: glob</name>
|
||||
<url>https://github.com/rust-lang/glob/blob/master/LICENSE-APACHE</url>
|
||||
</license>
|
||||
<license>
|
||||
<name>Apache License 2.0: hashbrown</name>
|
||||
<url>https://github.com/rust-lang/hashbrown/blob/master/LICENSE-APACHE</url>
|
||||
|
|
|
@ -9,7 +9,7 @@ use std::ffi::CString;
|
|||
use std::os::raw::c_char;
|
||||
|
||||
pub use autofill;
|
||||
pub use fxaclient_ffi;
|
||||
pub use fxa_client;
|
||||
pub use logins_ffi;
|
||||
pub use nimbus;
|
||||
pub use places_ffi;
|
||||
|
|
|
@ -11,7 +11,9 @@ the details of which are reproduced below.
|
|||
* [MIT License: atty](#mit-license-atty)
|
||||
* [MIT License: bincode](#mit-license-bincode)
|
||||
* [MIT License: bytes](#mit-license-bytes)
|
||||
* [MIT License: cargo_metadata](#mit-license-cargo_metadata)
|
||||
* [MIT License: caseless](#mit-license-caseless)
|
||||
* [MIT License: clap](#mit-license-clap)
|
||||
* [MIT License: dashmap](#mit-license-dashmap)
|
||||
* [MIT License: h2](#mit-license-h2)
|
||||
* [MIT License: http-body](#mit-license-http-body)
|
||||
|
@ -21,13 +23,16 @@ the details of which are reproduced below.
|
|||
* [MIT License: mime_guess](#mit-license-mime_guess)
|
||||
* [MIT License: miniz_oxide](#mit-license-miniz_oxide)
|
||||
* [MIT License: mio](#mit-license-mio)
|
||||
* [MIT License: nom](#mit-license-nom)
|
||||
* [MIT License: ordered-float](#mit-license-ordered-float)
|
||||
* [MIT License: oslog](#mit-license-oslog)
|
||||
* [MIT License: slab](#mit-license-slab)
|
||||
* [MIT License: textwrap](#mit-license-textwrap)
|
||||
* [MIT License: tokio, tokio-tls, tokio-util, tracing, tracing-core, tracing-futures](#mit-license-tokio-tokio-tls-tokio-util-tracing-tracing-core-tracing-futures)
|
||||
* [MIT License: tower-service](#mit-license-tower-service)
|
||||
* [MIT License: try-lock](#mit-license-try-lock)
|
||||
* [MIT License: want](#mit-license-want)
|
||||
* [MIT License: weedle](#mit-license-weedle)
|
||||
* [CC0-1.0 License: base16](#cc0-10-license-base16)
|
||||
* [ISC License: ring](#isc-license-ring)
|
||||
* [BSD-2-Clause License: arrayref](#bsd-2-clause-license-arrayref)
|
||||
|
@ -40,7 +45,11 @@ The following text applies to code linked from these dependencies:
|
|||
[NSPR](https://hg.mozilla.org/projects/nspr),
|
||||
[NSS](https://hg.mozilla.org/projects/nss),
|
||||
[ece](https://github.com/mozilla/rust-ece),
|
||||
[hawk](https://github.com/taskcluster/rust-hawk)
|
||||
[hawk](https://github.com/taskcluster/rust-hawk),
|
||||
[uniffi](https://github.com/mozilla/uniffi-rs),
|
||||
[uniffi_bindgen](https://github.com/mozilla/uniffi-rs),
|
||||
[uniffi_build](https://github.com/mozilla/uniffi-rs),
|
||||
[uniffi_macros](https://github.com/mozilla/uniffi-rs)
|
||||
|
||||
```
|
||||
Mozilla Public License Version 2.0
|
||||
|
@ -425,6 +434,10 @@ The following text applies to code linked from these dependencies:
|
|||
[adler](https://github.com/jonas-schievink/adler.git),
|
||||
[ahash](https://github.com/tkaitchuck/ahash),
|
||||
[anyhow](https://github.com/dtolnay/anyhow),
|
||||
[askama](https://github.com/djc/askama),
|
||||
[askama_derive](https://github.com/djc/askama),
|
||||
[askama_escape](https://github.com/djc/askama),
|
||||
[askama_shared](https://github.com/djc/askama),
|
||||
[autocfg](https://github.com/cuviper/autocfg),
|
||||
[base64](https://github.com/marshallpierce/rust-base64),
|
||||
[bitflags](https://github.com/bitflags/bitflags),
|
||||
|
@ -453,8 +466,10 @@ The following text applies to code linked from these dependencies:
|
|||
[futures-task](https://github.com/rust-lang/futures-rs),
|
||||
[futures-util](https://github.com/rust-lang/futures-rs),
|
||||
[getrandom](https://github.com/rust-random/getrandom),
|
||||
[glob](https://github.com/rust-lang/glob),
|
||||
[hashbrown](https://github.com/rust-lang/hashbrown),
|
||||
[hashlink](https://github.com/kyren/hashlink),
|
||||
[heck](https://github.com/withoutboats/heck),
|
||||
[hex](https://github.com/KokaKiwi/rust-hex),
|
||||
[http](https://github.com/hyperium/http),
|
||||
[httparse](https://github.com/seanmonstar/httparse),
|
||||
|
@ -506,6 +521,8 @@ The following text applies to code linked from these dependencies:
|
|||
[ryu](https://github.com/dtolnay/ryu),
|
||||
[security-framework-sys](https://github.com/kornelski/rust-security-framework),
|
||||
[security-framework](https://github.com/kornelski/rust-security-framework),
|
||||
[semver-parser](https://github.com/steveklabnik/semver-parser),
|
||||
[semver](https://github.com/steveklabnik/semver),
|
||||
[serde](https://github.com/serde-rs/serde),
|
||||
[serde_derive](https://github.com/serde-rs/serde),
|
||||
[serde_json](https://github.com/serde-rs/json),
|
||||
|
@ -513,6 +530,7 @@ The following text applies to code linked from these dependencies:
|
|||
[smallbitvec](https://github.com/servo/smallbitvec),
|
||||
[smallvec](https://github.com/servo/rust-smallvec),
|
||||
[socket2](https://github.com/alexcrichton/socket2-rs),
|
||||
[static_assertions](https://github.com/nvzqz/static-assertions-rs),
|
||||
[swift-protobuf](https://github.com/apple/swift-protobuf),
|
||||
[syn](https://github.com/dtolnay/syn),
|
||||
[tempfile](https://github.com/Stebalien/tempfile),
|
||||
|
@ -521,9 +539,12 @@ The following text applies to code linked from these dependencies:
|
|||
[thread_local](https://github.com/Amanieu/thread_local-rs),
|
||||
[time](https://github.com/time-rs/time),
|
||||
[tinyvec](https://github.com/Lokathor/tinyvec),
|
||||
[toml](https://github.com/alexcrichton/toml-rs),
|
||||
[unicase](https://github.com/seanmonstar/unicase),
|
||||
[unicode-bidi](https://github.com/servo/unicode-bidi),
|
||||
[unicode-normalization](https://github.com/unicode-rs/unicode-normalization),
|
||||
[unicode-segmentation](https://github.com/unicode-rs/unicode-segmentation),
|
||||
[unicode-width](https://github.com/unicode-rs/unicode-width),
|
||||
[unicode-xid](https://github.com/unicode-rs/unicode-xid),
|
||||
[url](https://github.com/servo/rust-url),
|
||||
[uuid](https://github.com/uuid-rs/uuid),
|
||||
|
@ -889,6 +910,38 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
|||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
```
|
||||
-------------
|
||||
## MIT License: cargo_metadata
|
||||
|
||||
The following text applies to code linked from these dependencies:
|
||||
[cargo_metadata](https://github.com/oli-obk/cargo_metadata)
|
||||
|
||||
```
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
```
|
||||
-------------
|
||||
## MIT License: caseless
|
||||
|
@ -920,6 +973,36 @@ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
```
|
||||
-------------
|
||||
## MIT License: clap
|
||||
|
||||
The following text applies to code linked from these dependencies:
|
||||
[clap](https://github.com/clap-rs/clap)
|
||||
|
||||
```
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-2016 Kevin B. Knapp
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
```
|
||||
-------------
|
||||
## MIT License: dashmap
|
||||
|
@ -1199,6 +1282,35 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
```
|
||||
-------------
|
||||
## MIT License: nom
|
||||
|
||||
The following text applies to code linked from these dependencies:
|
||||
[nom](https://github.com/Geal/nom)
|
||||
|
||||
```
|
||||
Copyright (c) 2014-2019 Geoffroy Couprie
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
```
|
||||
-------------
|
||||
## MIT License: ordered-float
|
||||
|
@ -1297,6 +1409,36 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
|||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
```
|
||||
-------------
|
||||
## MIT License: textwrap
|
||||
|
||||
The following text applies to code linked from these dependencies:
|
||||
[textwrap](https://github.com/mgeisler/textwrap)
|
||||
|
||||
```
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 Martin Geisler
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
```
|
||||
-------------
|
||||
## MIT License: tokio, tokio-tls, tokio-util, tracing, tracing-core, tracing-futures
|
||||
|
@ -1429,6 +1571,29 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|||
THE SOFTWARE.
|
||||
|
||||
|
||||
```
|
||||
-------------
|
||||
## MIT License: weedle
|
||||
|
||||
The following text applies to code linked from these dependencies:
|
||||
[weedle](https://github.com/rustwasm/weedle)
|
||||
|
||||
```
|
||||
Copyright 2018-Present Sharad Chand
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
```
|
||||
-------------
|
||||
## CC0-1.0 License: base16
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
FOUNDATION_EXPORT double MegazordClientVersionNumber;
|
||||
FOUNDATION_EXPORT const unsigned char MegazordClientVersionString[];
|
||||
|
||||
#import "RustFxAFFI.h"
|
||||
#import "uniffi_fxa_client-Bridging-Header.h"
|
||||
#import "RustPasswordAPI.h"
|
||||
#import "RustLogFFI.h"
|
||||
#import "RustPlacesAPI.h"
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
/* Begin PBXBuildFile section */
|
||||
1379D0692404BE1300AABD16 /* logins_msg_types.proto in Sources */ = {isa = PBXBuildFile; fileRef = 1379D0682404BE1300AABD16 /* logins_msg_types.proto */; };
|
||||
137C5893240257340016932F /* Data+LoginsRustBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 137C5892240257340016932F /* Data+LoginsRustBuffer.swift */; };
|
||||
78B6532724B39B2400ED1555 /* FxAccountMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B6532624B39B2400ED1555 /* FxAccountMetrics.swift */; };
|
||||
99FAA19B25E61D5D001E2231 /* uniffi_fxa_client-Bridging-Header.h in Headers */ = {isa = PBXBuildFile; fileRef = 99FAA19A25E61D5D001E2231 /* uniffi_fxa_client-Bridging-Header.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
99FAA19F25E65CA5001E2231 /* FxAccountOAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99FAA19E25E65CA5001E2231 /* FxAccountOAuth.swift */; };
|
||||
BF1A879025064A4C00FED88E /* Dispatchers.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1A878C25064A4C00FED88E /* Dispatchers.swift */; };
|
||||
BF1A879125064A4C00FED88E /* Glean.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1A878D25064A4C00FED88E /* Glean.swift */; };
|
||||
BF1A879225064A4C00FED88E /* GleanFfi.h in Headers */ = {isa = PBXBuildFile; fileRef = BF1A878E25064A4C00FED88E /* GleanFfi.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
|
@ -53,9 +54,6 @@
|
|||
C852EED6220A29FE00A6E79A /* LoginsStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C852EECD220A29FE00A6E79A /* LoginsStorage.swift */; };
|
||||
C852EED7220A29FE00A6E79A /* LoginStoreError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C852EECF220A29FE00A6E79A /* LoginStoreError.swift */; };
|
||||
C852EED8220A29FE00A6E79A /* LockError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C852EED0220A29FE00A6E79A /* LockError.swift */; };
|
||||
C852EEE7220A2A2B00A6E79A /* FirefoxAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = C852EEDD220A2A2B00A6E79A /* FirefoxAccount.swift */; };
|
||||
C852EEE8220A2A2B00A6E79A /* String+Free_FxAClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = C852EEDF220A2A2B00A6E79A /* String+Free_FxAClient.swift */; };
|
||||
C852EEEB220A2A2B00A6E79A /* FxAError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C852EEE3220A2A2B00A6E79A /* FxAError.swift */; };
|
||||
C852EEEF220A2E9400A6E79A /* libmegazord_ios.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C852EEEE220A2E9400A6E79A /* libmegazord_ios.a */; };
|
||||
CD02CF3922568BCC00124DA2 /* SyncUnlockInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD02CF3822568BCC00124DA2 /* SyncUnlockInfo.swift */; };
|
||||
CD440A422238A08E003F004B /* SwiftProtobuf.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EB7DE84C2214D30B00E7CF17 /* SwiftProtobuf.framework */; };
|
||||
|
@ -67,15 +65,14 @@
|
|||
CD85A45822361E890099BFA9 /* Data+PlacesRustBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD85A44E22361E880099BFA9 /* Data+PlacesRustBuffer.swift */; };
|
||||
CD85A45922361E890099BFA9 /* Bookmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD85A44F22361E880099BFA9 /* Bookmark.swift */; };
|
||||
CD85A45A22361E890099BFA9 /* PlacesError.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD85A45122361E880099BFA9 /* PlacesError.swift */; };
|
||||
CDC0089D2236CAB900893800 /* fxa_msg_types.proto in Sources */ = {isa = PBXBuildFile; fileRef = CDC0089C2236CAB900893800 /* fxa_msg_types.proto */; };
|
||||
CDC0089F2236CAD100893800 /* places_msg_types.proto in Sources */ = {isa = PBXBuildFile; fileRef = CDC0089E2236CAD100893800 /* places_msg_types.proto */; };
|
||||
CDC21B14221DCE3700AA71E5 /* RustLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDC21B12221DCE3700AA71E5 /* RustLog.swift */; };
|
||||
CDC21B15221DCE3700AA71E5 /* RustLogFFI.h in Headers */ = {isa = PBXBuildFile; fileRef = CDC21B13221DCE3700AA71E5 /* RustLogFFI.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
CE0A9AB424E4A2CC00914A16 /* fxa_client.udl in Sources */ = {isa = PBXBuildFile; fileRef = CE0A9AB224E4A25C00914A16 /* fxa_client.udl */; };
|
||||
CE13F19B23F330DF005187A7 /* FxAccountMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE13F19A23F330DF005187A7 /* FxAccountMigration.swift */; };
|
||||
CE13F19D23F448D0005187A7 /* FxAccountState.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE13F19C23F448D0005187A7 /* FxAccountState.swift */; };
|
||||
CE1445AC23D6058B00B1E808 /* FxAccountConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1445AB23D6058B00B1E808 /* FxAccountConfig.swift */; };
|
||||
CE1445AF23D6315200B1E808 /* FxAccountLogging.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1445AE23D6315200B1E808 /* FxAccountLogging.swift */; };
|
||||
CE1ADA9722249FDA00E89714 /* Data+RustBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1ADA9622249FDA00E89714 /* Data+RustBuffer.swift */; };
|
||||
CE1B09A3231863D7006226E1 /* KeychainWrapper+.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1B09A2231863D7006226E1 /* KeychainWrapper+.swift */; };
|
||||
CE1B09A5231865BC006226E1 /* FxAccountStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1B09A4231865BC006226E1 /* FxAccountStorage.swift */; };
|
||||
CE2D04C5231822AC00AF5722 /* FxAccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE2D04C4231822AC00AF5722 /* FxAccountManager.swift */; };
|
||||
|
@ -86,15 +83,10 @@
|
|||
CE96459C23D7A7D500B662F8 /* SwiftKeychainWrapper.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE96459823D7A77500B662F8 /* SwiftKeychainWrapper.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
CE9FFA13242D4E7B0011029E /* Viaduct.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE9FFA12242D4E7B0011029E /* Viaduct.swift */; };
|
||||
CEB1A06823D8A42D005BD4DD /* FxAccountMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEB1A06723D8A42D005BD4DD /* FxAccountMocks.swift */; };
|
||||
CED443CB23CCF385007E5FA6 /* FxAccountDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED443CA23CCF385007E5FA6 /* FxAccountDevice.swift */; };
|
||||
CED443CD23CD13E5007E5FA6 /* FxAccountDeviceConstellation.swift in Sources */ = {isa = PBXBuildFile; fileRef = CED443CC23CD13E5007E5FA6 /* FxAccountDeviceConstellation.swift */; };
|
||||
CEDDBC8C23DB438F00CFF5AA /* RustFxAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDDBC8B23DB438F00CFF5AA /* RustFxAccount.swift */; };
|
||||
CEDDBC8E23DB4CD600CFF5AA /* FxAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDDBC8D23DB4CD600CFF5AA /* FxAccount.swift */; };
|
||||
CEDDBC9023DB4F7400CFF5AA /* FxAccountOAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDDBC8F23DB4F7400CFF5AA /* FxAccountOAuth.swift */; };
|
||||
CEDDBC9223DB4F9A00CFF5AA /* FxAccountProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDDBC9123DB4F9A00CFF5AA /* FxAccountProfile.swift */; };
|
||||
CEDDBC8E23DB4CD600CFF5AA /* PersistedFirefoxAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDDBC8D23DB4CD600CFF5AA /* PersistedFirefoxAccount.swift */; };
|
||||
CEFB1EB022EF708B0001E20F /* ResultError.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEFB1EAF22EF708B0001E20F /* ResultError.swift */; };
|
||||
D05434A1225680D900FDE4EF /* MozillaAppServices.h in Headers */ = {isa = PBXBuildFile; fileRef = C852EEF2220A3C6800A6E79A /* MozillaAppServices.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D05434A2225680F400FDE4EF /* RustFxAFFI.h in Headers */ = {isa = PBXBuildFile; fileRef = C852EEE0220A2A2B00A6E79A /* RustFxAFFI.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D05434A32256810200FDE4EF /* RustPasswordAPI.h in Headers */ = {isa = PBXBuildFile; fileRef = C852EEC8220A29FE00A6E79A /* RustPasswordAPI.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
EB7DE84D2214D30B00E7CF17 /* SwiftProtobuf.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EB7DE84C2214D30B00E7CF17 /* SwiftProtobuf.framework */; };
|
||||
EB879D7F221234EB00753DC9 /* MozillaAppServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE9D202020914D0D00F1C8FA /* MozillaAppServices.framework */; };
|
||||
|
@ -102,6 +94,20 @@
|
|||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXBuildRule section */
|
||||
CE0617CE24E4978C0068E903 /* PBXBuildRule */ = {
|
||||
isa = PBXBuildRule;
|
||||
compilerSpec = com.apple.compilers.proxy.script;
|
||||
filePatterns = "*.udl";
|
||||
fileType = pattern.proxy;
|
||||
inputFiles = (
|
||||
);
|
||||
isEditable = 1;
|
||||
outputFiles = (
|
||||
"$(INPUT_FILE_DIR)/../ios/Generated/$(INPUT_FILE_BASE).swift",
|
||||
"$(INPUT_FILE_DIR)/../ios/Generated/uniffi_$(INPUT_FILE_BASE)-Bridging-Header.h",
|
||||
);
|
||||
script = "$HOME/.cargo/bin/cargo uniffi-bindgen generate $INPUT_FILE_PATH --language swift --out-dir $INPUT_FILE_DIR/../ios/Generated\n";
|
||||
};
|
||||
EB7DE84A2214D28C00E7CF17 /* PBXBuildRule */ = {
|
||||
isa = PBXBuildRule;
|
||||
compilerSpec = com.apple.compilers.proxy.script;
|
||||
|
@ -144,7 +150,9 @@
|
|||
/* Begin PBXFileReference section */
|
||||
1379D0682404BE1300AABD16 /* logins_msg_types.proto */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.protobuf; name = logins_msg_types.proto; path = ../../src/logins_msg_types.proto; sourceTree = "<group>"; };
|
||||
137C5892240257340016932F /* Data+LoginsRustBuffer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Data+LoginsRustBuffer.swift"; path = "Extensions/Data+LoginsRustBuffer.swift"; sourceTree = "<group>"; };
|
||||
78B6532624B39B2400ED1555 /* FxAccountMetrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FxAccountMetrics.swift; sourceTree = "<group>"; };
|
||||
99FAA19125E60D57001E2231 /* fxa_client.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = fxa_client.swift; path = ../../Generated/fxa_client.swift; sourceTree = "<group>"; };
|
||||
99FAA19A25E61D5D001E2231 /* uniffi_fxa_client-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "uniffi_fxa_client-Bridging-Header.h"; path = "../../Generated/uniffi_fxa_client-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
99FAA19E25E65CA5001E2231 /* FxAccountOAuth.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FxAccountOAuth.swift; sourceTree = "<group>"; };
|
||||
BF1A878C25064A4C00FED88E /* Dispatchers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Dispatchers.swift; sourceTree = "<group>"; };
|
||||
BF1A878D25064A4C00FED88E /* Glean.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Glean.swift; sourceTree = "<group>"; };
|
||||
BF1A878E25064A4C00FED88E /* GleanFfi.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GleanFfi.h; sourceTree = "<group>"; };
|
||||
|
@ -190,10 +198,6 @@
|
|||
C852EECD220A29FE00A6E79A /* LoginsStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginsStorage.swift; sourceTree = "<group>"; };
|
||||
C852EECF220A29FE00A6E79A /* LoginStoreError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginStoreError.swift; sourceTree = "<group>"; };
|
||||
C852EED0220A29FE00A6E79A /* LockError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LockError.swift; sourceTree = "<group>"; };
|
||||
C852EEDD220A2A2B00A6E79A /* FirefoxAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FirefoxAccount.swift; sourceTree = "<group>"; };
|
||||
C852EEDF220A2A2B00A6E79A /* String+Free_FxAClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Free_FxAClient.swift"; sourceTree = "<group>"; };
|
||||
C852EEE0220A2A2B00A6E79A /* RustFxAFFI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RustFxAFFI.h; sourceTree = "<group>"; };
|
||||
C852EEE3220A2A2B00A6E79A /* FxAError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FxAError.swift; sourceTree = "<group>"; };
|
||||
C852EEEE220A2E9400A6E79A /* libmegazord_ios.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libmegazord_ios.a; path = ../../target/universal/release/libmegazord_ios.a; sourceTree = "<group>"; };
|
||||
C852EEF2220A3C6800A6E79A /* MozillaAppServices.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MozillaAppServices.h; sourceTree = "<group>"; };
|
||||
CD02CF3822568BCC00124DA2 /* SyncUnlockInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncUnlockInfo.swift; sourceTree = "<group>"; };
|
||||
|
@ -204,15 +208,14 @@
|
|||
CD85A44E22361E880099BFA9 /* Data+PlacesRustBuffer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+PlacesRustBuffer.swift"; sourceTree = "<group>"; };
|
||||
CD85A44F22361E880099BFA9 /* Bookmark.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bookmark.swift; sourceTree = "<group>"; };
|
||||
CD85A45122361E880099BFA9 /* PlacesError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlacesError.swift; sourceTree = "<group>"; };
|
||||
CDC0089C2236CAB900893800 /* fxa_msg_types.proto */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.protobuf; name = fxa_msg_types.proto; path = ../../src/fxa_msg_types.proto; sourceTree = "<group>"; };
|
||||
CDC0089E2236CAD100893800 /* places_msg_types.proto */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.protobuf; name = places_msg_types.proto; path = ../../src/places_msg_types.proto; sourceTree = "<group>"; };
|
||||
CDC21B12221DCE3700AA71E5 /* RustLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RustLog.swift; sourceTree = "<group>"; };
|
||||
CDC21B13221DCE3700AA71E5 /* RustLogFFI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RustLogFFI.h; sourceTree = "<group>"; };
|
||||
CE0A9AB224E4A25C00914A16 /* fxa_client.udl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = fxa_client.udl; path = ../../src/fxa_client.udl; sourceTree = "<group>"; };
|
||||
CE13F19A23F330DF005187A7 /* FxAccountMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FxAccountMigration.swift; sourceTree = "<group>"; };
|
||||
CE13F19C23F448D0005187A7 /* FxAccountState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FxAccountState.swift; sourceTree = "<group>"; };
|
||||
CE1445AB23D6058B00B1E808 /* FxAccountConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FxAccountConfig.swift; sourceTree = "<group>"; };
|
||||
CE1445AE23D6315200B1E808 /* FxAccountLogging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FxAccountLogging.swift; sourceTree = "<group>"; };
|
||||
CE1ADA9622249FDA00E89714 /* Data+RustBuffer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+RustBuffer.swift"; sourceTree = "<group>"; };
|
||||
CE1B09A2231863D7006226E1 /* KeychainWrapper+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeychainWrapper+.swift"; sourceTree = "<group>"; };
|
||||
CE1B09A4231865BC006226E1 /* FxAccountStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FxAccountStorage.swift; sourceTree = "<group>"; };
|
||||
CE2D04C4231822AC00AF5722 /* FxAccountManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FxAccountManager.swift; sourceTree = "<group>"; };
|
||||
|
@ -223,12 +226,8 @@
|
|||
CE9D202020914D0D00F1C8FA /* MozillaAppServices.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MozillaAppServices.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
CE9FFA12242D4E7B0011029E /* Viaduct.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Viaduct.swift; sourceTree = "<group>"; };
|
||||
CEB1A06723D8A42D005BD4DD /* FxAccountMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FxAccountMocks.swift; sourceTree = "<group>"; };
|
||||
CED443CA23CCF385007E5FA6 /* FxAccountDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FxAccountDevice.swift; sourceTree = "<group>"; };
|
||||
CED443CC23CD13E5007E5FA6 /* FxAccountDeviceConstellation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FxAccountDeviceConstellation.swift; sourceTree = "<group>"; };
|
||||
CEDDBC8B23DB438F00CFF5AA /* RustFxAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RustFxAccount.swift; sourceTree = "<group>"; };
|
||||
CEDDBC8D23DB4CD600CFF5AA /* FxAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FxAccount.swift; sourceTree = "<group>"; };
|
||||
CEDDBC8F23DB4F7400CFF5AA /* FxAccountOAuth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FxAccountOAuth.swift; sourceTree = "<group>"; };
|
||||
CEDDBC9123DB4F9A00CFF5AA /* FxAccountProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FxAccountProfile.swift; sourceTree = "<group>"; };
|
||||
CEDDBC8D23DB4CD600CFF5AA /* PersistedFirefoxAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistedFirefoxAccount.swift; sourceTree = "<group>"; };
|
||||
CEFB1EAF22EF708B0001E20F /* ResultError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultError.swift; sourceTree = "<group>"; };
|
||||
EB7DE84C2214D30B00E7CF17 /* SwiftProtobuf.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftProtobuf.framework; path = ../../Carthage/Build/iOS/SwiftProtobuf.framework; sourceTree = "<group>"; };
|
||||
EB879D7A221234EB00753DC9 /* MozillaAppServicesTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MozillaAppServicesTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
|
@ -263,6 +262,15 @@
|
|||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
99FAA19025E60D2C001E2231 /* Generated */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
99FAA19A25E61D5D001E2231 /* uniffi_fxa_client-Bridging-Header.h */,
|
||||
99FAA19125E60D57001E2231 /* fxa_client.swift */,
|
||||
);
|
||||
path = Generated;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BF1A877F2506490700FED88E /* Glean */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -419,46 +427,22 @@
|
|||
C852EEDA220A2A2B00A6E79A /* FxAClient */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CDC0089C2236CAB900893800 /* fxa_msg_types.proto */,
|
||||
C852EEDD220A2A2B00A6E79A /* FirefoxAccount.swift */,
|
||||
CEDDBC8B23DB438F00CFF5AA /* RustFxAccount.swift */,
|
||||
CEDDBC8D23DB4CD600CFF5AA /* FxAccount.swift */,
|
||||
78B6532624B39B2400ED1555 /* FxAccountMetrics.swift */,
|
||||
99FAA19025E60D2C001E2231 /* Generated */,
|
||||
CE0A9AB224E4A25C00914A16 /* fxa_client.udl */,
|
||||
CEDDBC8D23DB4CD600CFF5AA /* PersistedFirefoxAccount.swift */,
|
||||
99FAA19E25E65CA5001E2231 /* FxAccountOAuth.swift */,
|
||||
CE1445AB23D6058B00B1E808 /* FxAccountConfig.swift */,
|
||||
CED443CA23CCF385007E5FA6 /* FxAccountDevice.swift */,
|
||||
CED443CC23CD13E5007E5FA6 /* FxAccountDeviceConstellation.swift */,
|
||||
CE1445AE23D6315200B1E808 /* FxAccountLogging.swift */,
|
||||
CE2D04C4231822AC00AF5722 /* FxAccountManager.swift */,
|
||||
CE13F19A23F330DF005187A7 /* FxAccountMigration.swift */,
|
||||
CEDDBC8F23DB4F7400CFF5AA /* FxAccountOAuth.swift */,
|
||||
CEDDBC9123DB4F9A00CFF5AA /* FxAccountProfile.swift */,
|
||||
CE13F19C23F448D0005187A7 /* FxAccountState.swift */,
|
||||
CE1B09A4231865BC006226E1 /* FxAccountStorage.swift */,
|
||||
C852EEDE220A2A2B00A6E79A /* Extensions */,
|
||||
C852EEE0220A2A2B00A6E79A /* RustFxAFFI.h */,
|
||||
C852EEE2220A2A2B00A6E79A /* Errors */,
|
||||
);
|
||||
name = FxAClient;
|
||||
path = "../../components/fxa-client/ios/FxAClient";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C852EEDE220A2A2B00A6E79A /* Extensions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CE1ADA9622249FDA00E89714 /* Data+RustBuffer.swift */,
|
||||
C852EEDF220A2A2B00A6E79A /* String+Free_FxAClient.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C852EEE2220A2A2B00A6E79A /* Errors */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C852EEE3220A2A2B00A6E79A /* FxAError.swift */,
|
||||
);
|
||||
path = Errors;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CD85A44822361E880099BFA9 /* Places */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -585,11 +569,11 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D05434A1225680D900FDE4EF /* MozillaAppServices.h in Headers */,
|
||||
D05434A2225680F400FDE4EF /* RustFxAFFI.h in Headers */,
|
||||
D05434A32256810200FDE4EF /* RustPasswordAPI.h in Headers */,
|
||||
CDC21B15221DCE3700AA71E5 /* RustLogFFI.h in Headers */,
|
||||
CE58B2F8242D54340089F091 /* RustViaductFFI.h in Headers */,
|
||||
CD85A45522361E890099BFA9 /* RustPlacesAPI.h in Headers */,
|
||||
99FAA19B25E61D5D001E2231 /* uniffi_fxa_client-Bridging-Header.h in Headers */,
|
||||
BF1A879225064A4C00FED88E /* GleanFfi.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -609,6 +593,7 @@
|
|||
CE9D201E20914D0D00F1C8FA /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
CE0617CE24E4978C0068E903 /* PBXBuildRule */,
|
||||
EB7DE84A2214D28C00E7CF17 /* PBXBuildRule */,
|
||||
);
|
||||
dependencies = (
|
||||
|
@ -644,7 +629,7 @@
|
|||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1010;
|
||||
LastUpgradeCheck = 1010;
|
||||
LastUpgradeCheck = 1240;
|
||||
ORGANIZATIONNAME = Mozilla;
|
||||
TargetAttributes = {
|
||||
CE9D201F20914D0D00F1C8FA = {
|
||||
|
@ -743,11 +728,9 @@
|
|||
1379D0692404BE1300AABD16 /* logins_msg_types.proto in Sources */,
|
||||
BF1A87B525064A8100FED88E /* CounterMetric.swift in Sources */,
|
||||
CE13F19B23F330DF005187A7 /* FxAccountMigration.swift in Sources */,
|
||||
CEDDBC9023DB4F7400CFF5AA /* FxAccountOAuth.swift in Sources */,
|
||||
CE0A9AB424E4A2CC00914A16 /* fxa_client.udl in Sources */,
|
||||
137C5893240257340016932F /* Data+LoginsRustBuffer.swift in Sources */,
|
||||
BF1A879525064A6600FED88E /* Configuration.swift in Sources */,
|
||||
CE1ADA9722249FDA00E89714 /* Data+RustBuffer.swift in Sources */,
|
||||
C852EEE8220A2A2B00A6E79A /* String+Free_FxAClient.swift in Sources */,
|
||||
BF1A87D725064B4C00FED88E /* Metrics.swift in Sources */,
|
||||
CDC0089F2236CAD100893800 /* places_msg_types.proto in Sources */,
|
||||
BF1A87B025064A8100FED88E /* LabeledMetric.swift in Sources */,
|
||||
|
@ -757,27 +740,22 @@
|
|||
BF1A87BB25064A8100FED88E /* TimespanMetric.swift in Sources */,
|
||||
BF1A879025064A4C00FED88E /* Dispatchers.swift in Sources */,
|
||||
BF1A87C425064AA500FED88E /* GleanLifecycleObserver.swift in Sources */,
|
||||
CEDDBC8E23DB4CD600CFF5AA /* FxAccount.swift in Sources */,
|
||||
CEDDBC8C23DB438F00CFF5AA /* RustFxAccount.swift in Sources */,
|
||||
CEDDBC8E23DB4CD600CFF5AA /* PersistedFirefoxAccount.swift in Sources */,
|
||||
CED443CD23CD13E5007E5FA6 /* FxAccountDeviceConstellation.swift in Sources */,
|
||||
BF1A87B825064A8100FED88E /* JweMetric.swift in Sources */,
|
||||
BF1A87B225064A8100FED88E /* DatetimeMetric.swift in Sources */,
|
||||
BF1A87AA25064A8100FED88E /* RecordedExperimentData.swift in Sources */,
|
||||
BF1A879125064A4C00FED88E /* Glean.swift in Sources */,
|
||||
CEDDBC9223DB4F9A00CFF5AA /* FxAccountProfile.swift in Sources */,
|
||||
BF1A87C925064AB300FED88E /* ErrorType.swift in Sources */,
|
||||
C852EEEB220A2A2B00A6E79A /* FxAError.swift in Sources */,
|
||||
BF1A87BA25064A8100FED88E /* StringMetric.swift in Sources */,
|
||||
99FAA19F25E65CA5001E2231 /* FxAccountOAuth.swift in Sources */,
|
||||
C852EED7220A29FE00A6E79A /* LoginStoreError.swift in Sources */,
|
||||
BF1A87D025064AC000FED88E /* Sysctl.swift in Sources */,
|
||||
CD02CF3922568BCC00124DA2 /* SyncUnlockInfo.swift in Sources */,
|
||||
CDC0089D2236CAB900893800 /* fxa_msg_types.proto in Sources */,
|
||||
CD85A45A22361E890099BFA9 /* PlacesError.swift in Sources */,
|
||||
BF1A87AB25064A8100FED88E /* DistributionData.swift in Sources */,
|
||||
CE1445AC23D6058B00B1E808 /* FxAccountConfig.swift in Sources */,
|
||||
78B6532724B39B2400ED1555 /* FxAccountMetrics.swift in Sources */,
|
||||
CD85A45422361E890099BFA9 /* Places.swift in Sources */,
|
||||
CED443CB23CCF385007E5FA6 /* FxAccountDevice.swift in Sources */,
|
||||
C852EED8220A29FE00A6E79A /* LockError.swift in Sources */,
|
||||
BF1A87AC25064A8100FED88E /* MemoryUnit.swift in Sources */,
|
||||
BF1A87BE25064A9400FED88E /* HttpPingUploader.swift in Sources */,
|
||||
|
@ -790,7 +768,6 @@
|
|||
CD85A45722361E890099BFA9 /* String+Free_Places.swift in Sources */,
|
||||
C852EED5220A29FE00A6E79A /* LoginRecord.swift in Sources */,
|
||||
CE9FFA13242D4E7B0011029E /* Viaduct.swift in Sources */,
|
||||
C852EEE7220A2A2B00A6E79A /* FirefoxAccount.swift in Sources */,
|
||||
CD85A45822361E890099BFA9 /* Data+PlacesRustBuffer.swift in Sources */,
|
||||
BF1A87B925064A8100FED88E /* TimingDistributionMetric.swift in Sources */,
|
||||
CDC21B14221DCE3700AA71E5 /* RustLog.swift in Sources */,
|
||||
|
@ -874,6 +851,7 @@
|
|||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = mozilla.org.MozillaAppServicesTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
VALIDATE_WORKSPACE = NO;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
|
@ -888,6 +866,7 @@
|
|||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = mozilla.org.MozillaAppServicesTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
VALIDATE_WORKSPACE = NO;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1010"
|
||||
LastUpgradeVersion = "1240"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
@ -27,6 +27,15 @@
|
|||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "CE9D201F20914D0D00F1C8FA"
|
||||
BuildableName = "MozillaAppServices.framework"
|
||||
BlueprintName = "MozillaAppServices"
|
||||
ReferencedContainer = "container:MozillaAppServices.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
|
@ -39,17 +48,6 @@
|
|||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "CE9D201F20914D0D00F1C8FA"
|
||||
BuildableName = "MozillaAppServices.framework"
|
||||
BlueprintName = "MozillaAppServices"
|
||||
ReferencedContainer = "container:MozillaAppServices.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
|
@ -70,8 +68,6 @@
|
|||
ReferencedContainer = "container:MozillaAppServices.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
|
|
|
@ -173,7 +173,7 @@ class FxAccountManagerTests: XCTestCase {
|
|||
func testAccountRestorationEnsureCapabilitiesNonAuthError() {
|
||||
class MockAccount: MockFxAccount {
|
||||
override func ensureCapabilities(supportedCapabilities _: [DeviceCapability]) throws {
|
||||
throw FirefoxAccountError.network(message: "The WiFi cable is detached.")
|
||||
throw FxaError.Network(message: "The WiFi cable is detached.")
|
||||
}
|
||||
}
|
||||
let mgr = mockFxAManager()
|
||||
|
@ -203,12 +203,12 @@ class FxAccountManagerTests: XCTestCase {
|
|||
class MockAccount: MockFxAccount {
|
||||
override func ensureCapabilities(supportedCapabilities _: [DeviceCapability]) throws {
|
||||
notifyAuthError()
|
||||
throw FirefoxAccountError.unauthorized(message: "Your token is expired yo.")
|
||||
throw FxaError.Authentication(message: "Your token is expired yo.")
|
||||
}
|
||||
|
||||
override func checkAuthorizationStatus() throws -> IntrospectInfo {
|
||||
override func checkAuthorizationStatus() throws -> AuthorizationInfo {
|
||||
_ = try super.checkAuthorizationStatus()
|
||||
return IntrospectInfo(active: false)
|
||||
return AuthorizationInfo(active: false)
|
||||
}
|
||||
}
|
||||
let mgr = mockFxAManager()
|
||||
|
@ -301,7 +301,7 @@ class FxAccountManagerTests: XCTestCase {
|
|||
profileCallCount += 1
|
||||
if profileCallCount == 1 {
|
||||
notifyAuthError()
|
||||
throw FirefoxAccountError.unauthorized(message: "Uh oh.")
|
||||
throw FxaError.Authentication(message: "Uh oh.")
|
||||
} else {
|
||||
return profile
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import Foundation
|
|||
// Arrays are not thread-safe in Swift.
|
||||
let queue = DispatchQueue(label: "InvocationsArrayQueue")
|
||||
|
||||
class MockFxAccount: FxAccount {
|
||||
class MockFxAccount: PersistedFirefoxAccount {
|
||||
var invocations: [MethodInvocation] = []
|
||||
enum MethodInvocation {
|
||||
case checkAuthorizationStatus
|
||||
|
@ -22,14 +22,14 @@ class MockFxAccount: FxAccount {
|
|||
}
|
||||
|
||||
init() {
|
||||
super.init(raw: 0)
|
||||
super.init(inner: FirefoxAccount(contentUrl: "", clientId: "", redirectUri: "", tokenServerUrlOverride: nil))
|
||||
}
|
||||
|
||||
required convenience init(fromJsonState _: String) throws {
|
||||
fatalError("init(fromJsonState:) has not been implemented")
|
||||
}
|
||||
|
||||
required convenience init(config _: FxAConfig) throws {
|
||||
required convenience init(config _: FxAConfig) {
|
||||
fatalError("init(config:) has not been implemented")
|
||||
}
|
||||
|
||||
|
@ -54,46 +54,46 @@ class MockFxAccount: FxAccount {
|
|||
queue.sync { invocations.append(.ensureCapabilities) }
|
||||
}
|
||||
|
||||
override func checkAuthorizationStatus() throws -> IntrospectInfo {
|
||||
override func checkAuthorizationStatus() throws -> AuthorizationInfo {
|
||||
queue.sync { invocations.append(.checkAuthorizationStatus) }
|
||||
return IntrospectInfo(active: true)
|
||||
return AuthorizationInfo(active: true)
|
||||
}
|
||||
|
||||
override func clearAccessTokenCache() throws {
|
||||
override func clearAccessTokenCache() {
|
||||
queue.sync { invocations.append(.clearAccessTokenCache) }
|
||||
}
|
||||
|
||||
override func getAccessToken(scope _: String, ttl _: UInt64? = nil) throws -> AccessTokenInfo {
|
||||
queue.sync { invocations.append(.getAccessToken) }
|
||||
return AccessTokenInfo(scope: "profile", token: "toktok")
|
||||
return AccessTokenInfo(scope: "profile", token: "toktok", key: nil, expiresAt: Int64.max)
|
||||
}
|
||||
|
||||
override func getProfile(ignoreCache _: Bool) throws -> Profile {
|
||||
queue.sync { invocations.append(.getProfile) }
|
||||
return Profile(uid: "uid", email: "foo@bar.bobo")
|
||||
return Profile(uid: "uid", email: "foo@bar.bobo", displayName: "Bobo the Foo", avatar: "https://example.com/avatar.png", isDefaultAvatar: false)
|
||||
}
|
||||
|
||||
override func beginOAuthFlow(
|
||||
scopes _: [String],
|
||||
entrypoint _: String,
|
||||
metricsParams _: MetricsParams = MetricsParams.newEmpty()
|
||||
metrics _: MetricsParams = MetricsParams(parameters: [:])
|
||||
) throws -> URL {
|
||||
return URL(string: "https://foo.bar/oauth?state=bobo")!
|
||||
}
|
||||
}
|
||||
|
||||
class MockFxAccountManager: FxAccountManager {
|
||||
var storedAccount: FxAccount?
|
||||
var storedAccount: PersistedFirefoxAccount?
|
||||
|
||||
override func createAccount() -> FxAccount {
|
||||
override func createAccount() -> PersistedFirefoxAccount {
|
||||
return MockFxAccount()
|
||||
}
|
||||
|
||||
override func makeDeviceConstellation(account _: FxAccount) -> DeviceConstellation {
|
||||
override func makeDeviceConstellation(account _: PersistedFirefoxAccount) -> DeviceConstellation {
|
||||
return MockDeviceConstellation(account: account)
|
||||
}
|
||||
|
||||
override func tryRestoreAccount() -> FxAccount? {
|
||||
override func tryRestoreAccount() -> PersistedFirefoxAccount? {
|
||||
return storedAccount
|
||||
}
|
||||
}
|
||||
|
@ -106,7 +106,7 @@ class MockDeviceConstellation: DeviceConstellation {
|
|||
case refreshState
|
||||
}
|
||||
|
||||
override init(account: FxAccount?) {
|
||||
override init(account: PersistedFirefoxAccount?) {
|
||||
super.init(account: account ?? MockFxAccount())
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ license = "MPL-2.0"
|
|||
crate-type = ["staticlib"]
|
||||
|
||||
[dependencies]
|
||||
fxaclient_ffi = { path = "../../../components/fxa-client/ffi" }
|
||||
fxa-client = { path = "../../../components/fxa-client" }
|
||||
logins_ffi = { path = "../../../components/logins/ffi" }
|
||||
places-ffi = { path = "../../../components/places/ffi" }
|
||||
rc_log_ffi = { path = "../../../components/rc_log" }
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
#![allow(unknown_lints)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
pub use fxaclient_ffi;
|
||||
pub use fxa_client;
|
||||
pub use glean_ffi;
|
||||
pub use logins_ffi;
|
||||
pub use places_ffi;
|
||||
|
|
|
@ -3,7 +3,7 @@ http://creativecommons.org/publicdomain/zero/1.0/ */
|
|||
|
||||
use crate::Opts;
|
||||
use anyhow::Result;
|
||||
use fxa_client::{self, auth, Config as FxaConfig, FirefoxAccount};
|
||||
use fxa_client::internal::{auth, config::Config as FxaConfig, FirefoxAccount};
|
||||
use logins::PasswordStore;
|
||||
use serde_json::json;
|
||||
use std::collections::HashMap;
|
||||
|
@ -220,7 +220,7 @@ impl Drop for TestAccount {
|
|||
}
|
||||
|
||||
pub struct TestClient {
|
||||
pub fxa: fxa_client::FirefoxAccount,
|
||||
pub fxa: fxa_client::internal::FirefoxAccount,
|
||||
pub test_acct: Arc<TestAccount>,
|
||||
// XXX do this more generically...
|
||||
pub logins_store: PasswordStore,
|
||||
|
@ -257,7 +257,11 @@ impl TestClient {
|
|||
fxa.complete_oauth_flow(&code, &state)?;
|
||||
log::info!("OAuth flow finished");
|
||||
|
||||
fxa.initialize_device("Testing Device", fxa_client::device::Type::Desktop, &[])?;
|
||||
fxa.initialize_device(
|
||||
"Testing Device",
|
||||
fxa_client::internal::device::Type::Desktop,
|
||||
&[],
|
||||
)?;
|
||||
|
||||
Ok(Self {
|
||||
fxa,
|
||||
|
|
|
@ -599,6 +599,16 @@ PACKAGE_METADATA_FIXUPS = {
|
|||
"fixup": "https://raw.githubusercontent.com/mozilla/uniffi-rs/main/LICENSE",
|
||||
}
|
||||
},
|
||||
"uniffi_macros": {
|
||||
"license_url": {
|
||||
"check": None,
|
||||
"fixup": "https://github.com/mozilla/uniffi-rs/blob/main/LICENSE",
|
||||
},
|
||||
"license_file": {
|
||||
"check": None,
|
||||
"fixup": "https://raw.githubusercontent.com/mozilla/uniffi-rs/main/LICENSE",
|
||||
}
|
||||
},
|
||||
"uniffi": {
|
||||
"license_url": {
|
||||
"check": None,
|
||||
|
|
|
@ -2,9 +2,6 @@
|
|||
# `dir` refers to the directory where the protobuf file can be found, relative to this file location.
|
||||
# (Optional) `out_dir` refers to where the compiled rust file should be saved. If not present `dir` is used.
|
||||
|
||||
["fxa_msg_types.proto"]
|
||||
dir = "../components/fxa-client/src/"
|
||||
|
||||
["logins_msg_types.proto"]
|
||||
dir = "../components/logins/src/"
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче