Merge branch 'rpc-auth-and-meta' into 'main'
rpc: authentication and basic handle manipulation See merge request tpo/core/arti!1200
This commit is contained in:
commit
3d4b9aa1b7
|
@ -1,12 +1,13 @@
|
|||
//! RPC connection support, mainloop, and protocol implementation.
|
||||
|
||||
mod auth;
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
pin::Pin,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use arti_client::TorClient;
|
||||
use asynchronous_codec::JsonCodecError;
|
||||
use futures::{
|
||||
channel::mpsc,
|
||||
|
@ -17,7 +18,6 @@ use pin_project::pin_project;
|
|||
use rpc::dispatch::BoxedUpdateSink;
|
||||
use serde_json::error::Category as JsonErrorCategory;
|
||||
use tor_async_utils::SinkExt as _;
|
||||
use tor_rtcompat::PreferredRuntime;
|
||||
|
||||
use crate::{
|
||||
cancel::{Cancel, CancelHandle},
|
||||
|
@ -32,7 +32,7 @@ use tor_rpcbase as rpc;
|
|||
///
|
||||
/// Tracks information that persists from one request to another.
|
||||
pub struct Connection {
|
||||
/// The mutable state of this connection
|
||||
/// The mutable state of this connection.
|
||||
inner: Mutex<Inner>,
|
||||
|
||||
/// Lookup table to find the implementations for methods
|
||||
|
@ -92,7 +92,7 @@ impl Connection {
|
|||
}
|
||||
|
||||
/// Look up a given object by its object ID relative to this connection.
|
||||
fn lookup_object(
|
||||
pub(crate) fn lookup_object(
|
||||
self: &Arc<Self>,
|
||||
id: &rpc::ObjectId,
|
||||
) -> Result<Arc<dyn rpc::Object>, rpc::LookupError> {
|
||||
|
@ -376,108 +376,29 @@ impl rpc::Context for RequestContext {
|
|||
.insert_weak(object)
|
||||
.encode()
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple temporary method to echo a reply.
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
struct Echo {
|
||||
/// A message to echo.
|
||||
msg: String,
|
||||
}
|
||||
rpc::decl_method! { "arti:x-echo" => Echo}
|
||||
impl rpc::Method for Echo {
|
||||
type Output = Echo;
|
||||
type Update = rpc::NoUpdates;
|
||||
}
|
||||
fn release_owned(&self, id: &rpc::ObjectId) -> Result<(), rpc::LookupError> {
|
||||
let idx = crate::objmap::GenIdx::try_decode(id)?;
|
||||
if !idx.is_strong() {
|
||||
return Err(rpc::LookupError::WrongType(id.clone()));
|
||||
}
|
||||
|
||||
/// Implementation for calling "echo" on a TorClient.
|
||||
///
|
||||
/// TODO RPC: Remove this. It shouldn't exist.
|
||||
async fn echo_on_session(
|
||||
_obj: Arc<TorClient<PreferredRuntime>>,
|
||||
method: Box<Echo>,
|
||||
_ctx: Box<dyn rpc::Context>,
|
||||
) -> Result<Echo, rpc::RpcError> {
|
||||
Ok(*method)
|
||||
}
|
||||
let removed = self
|
||||
.conn
|
||||
.inner
|
||||
.lock()
|
||||
.expect("Lock poisoned")
|
||||
.objects
|
||||
.remove(idx);
|
||||
|
||||
rpc::rpc_invoke_fn! {
|
||||
echo_on_session(TorClient<PreferredRuntime>,Echo);
|
||||
|
||||
}
|
||||
|
||||
/// The authentication scheme as enumerated in the spec.
|
||||
///
|
||||
/// Conceptually, an authentication scheme answers the question "How can the
|
||||
/// Arti process know you have permissions to use or administer it?"
|
||||
///
|
||||
/// TODO RPC: The only supported one for now is "inherent:unix_path"
|
||||
#[derive(Debug, Copy, Clone, serde::Deserialize)]
|
||||
enum AuthenticationScheme {
|
||||
/// Inherent authority based on the ability to access an AF_UNIX address.
|
||||
#[serde(rename = "inherent:unix_path")]
|
||||
InherentUnixPath,
|
||||
}
|
||||
|
||||
/// Method to implement basic authentication. Right now only "I connected to
|
||||
/// you so I must have permission!" is supported.
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct Authenticate {
|
||||
/// The authentication scheme as enumerated in the spec.
|
||||
///
|
||||
/// TODO RPC: The only supported one for now is "inherent:unix_path"
|
||||
scheme: AuthenticationScheme,
|
||||
}
|
||||
|
||||
/// A reply from the `Authenticate` method.
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
struct AuthenticateReply {
|
||||
/// An owned reference to a `TorClient` object.
|
||||
client: Option<rpc::ObjectId>,
|
||||
}
|
||||
|
||||
rpc::decl_method! {"auth:authenticate" => Authenticate}
|
||||
impl rpc::Method for Authenticate {
|
||||
type Output = AuthenticateReply;
|
||||
type Update = rpc::NoUpdates;
|
||||
}
|
||||
|
||||
/// An error during authentication.
|
||||
#[derive(Debug, Clone, thiserror::Error, serde::Serialize)]
|
||||
enum AuthenticationFailure {}
|
||||
|
||||
impl tor_error::HasKind for AuthenticationFailure {
|
||||
fn kind(&self) -> tor_error::ErrorKind {
|
||||
// TODO RPC not right.
|
||||
tor_error::ErrorKind::LocalProtocolViolation
|
||||
if removed.is_some() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(rpc::LookupError::NoObject(id.clone()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoke the "authenticate" method on a connection.
|
||||
///
|
||||
/// TODO RPC: This behavior is wrong; we'll need to fix it to be all
|
||||
/// capabilities-like.
|
||||
async fn authenticate_connection(
|
||||
unauth: Arc<Connection>,
|
||||
method: Box<Authenticate>,
|
||||
ctx: Box<dyn rpc::Context>,
|
||||
) -> Result<AuthenticateReply, rpc::RpcError> {
|
||||
match method.scheme {
|
||||
// For now, we only support AF_UNIX connections, and we assume that if
|
||||
// you have permission to open such a connection to us, you have
|
||||
// permission to use Arti. We will refine this later on!
|
||||
AuthenticationScheme::InherentUnixPath => {}
|
||||
}
|
||||
|
||||
let client = Arc::clone(&unauth.inner.lock().expect("Poisoned lock").client);
|
||||
|
||||
let client = Some(ctx.register_weak(client));
|
||||
Ok(AuthenticateReply { client })
|
||||
}
|
||||
rpc::rpc_invoke_fn! {
|
||||
authenticate_connection(Connection, Authenticate);
|
||||
}
|
||||
|
||||
/// An error given when an RPC request is cancelled.
|
||||
///
|
||||
/// This is a separate type from [`crate::cancel::Cancelled`] since eventually
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
//! RPC commands and related functionality for authentication.
|
||||
//!
|
||||
//! In Arti's RPC system, authentication is a kind of method that can be invoked
|
||||
//! on the special "connection" object, which gives you an RPC _session_ as a
|
||||
//! result. The RPC session is the root for all other capabilities.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::Connection;
|
||||
use tor_rpcbase as rpc;
|
||||
|
||||
/*
|
||||
TODO RPC: This is disabled because the design isn't really useful.
|
||||
If we're going to provide something here, it should probably
|
||||
contain a list of protocol elements/aspects, and it should be designed
|
||||
to enable compatibility, with a clear view of what applications are
|
||||
supposed to do about it.
|
||||
|
||||
/// Declare the get_rpc_protocol method.
|
||||
mod get_rpc_protocol {
|
||||
use super::Connection;
|
||||
use std::sync::Arc;
|
||||
use tor_rpcbase as rpc;
|
||||
|
||||
/// Method to inquire about the RPC protocol.
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct GetRpcProtocol {}
|
||||
|
||||
/// Reply to the [`GetRpcProtocol`] method
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
struct GetProtocolReply {
|
||||
/// The version of the RPC protocol that this server speaks.
|
||||
// TODO RPC: Should this be a list?
|
||||
version: RpcProtocolId,
|
||||
}
|
||||
|
||||
/// Identifier for a version of this RPC meta-protocol.
|
||||
#[derive(Debug, Copy, Clone, serde::Serialize)]
|
||||
enum RpcProtocolId {
|
||||
/// Alpha version of the protocol. Things might break between here and the
|
||||
/// stable protocol.
|
||||
///
|
||||
/// TODO RPC: CHange this to v0.
|
||||
#[serde(rename = "alpha")]
|
||||
Alpha,
|
||||
}
|
||||
rpc::decl_method! {"auth:get_rpc_protocol" => GetRpcProtocol}
|
||||
impl rpc::Method for GetRpcProtocol {
|
||||
type Output = GetProtocolReply;
|
||||
type Update = rpc::NoUpdates;
|
||||
}
|
||||
|
||||
/// Describe which version of the RPC protocol our connection implements.
|
||||
async fn conn_get_rpc_protocol(
|
||||
_conn: Arc<Connection>,
|
||||
_method: Box<GetRpcProtocol>,
|
||||
_ctx: Box<dyn rpc::Context>,
|
||||
) -> Result<GetProtocolReply, rpc::RpcError> {
|
||||
Ok(GetProtocolReply {
|
||||
version: RpcProtocolId::Alpha,
|
||||
})
|
||||
}
|
||||
rpc::rpc_invoke_fn! {
|
||||
conn_get_rpc_protocol(Connection, GetRpcProtocol);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/// The authentication scheme as enumerated in the spec.
|
||||
///
|
||||
/// Conceptually, an authentication scheme answers the question "How can the
|
||||
/// Arti process know you have permissions to use or administer it?"
|
||||
///
|
||||
/// TODO RPC: The only supported one for now is "inherent:unix_path"
|
||||
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||
enum AuthenticationScheme {
|
||||
/// Inherent authority based on the ability to access an AF_UNIX address.
|
||||
#[serde(rename = "inherent:unix_path")]
|
||||
InherentUnixPath,
|
||||
}
|
||||
|
||||
/// Method to ask which authentication methods are supported.
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct AuthQuery {}
|
||||
|
||||
/// A list of supported authentication schemes and their parameters.
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
struct SupportedAuth {
|
||||
/// A list of the supported authentication schemes.
|
||||
///
|
||||
/// TODO RPC: Actually, this should be able to contain strings _or_ maps,
|
||||
/// where the maps are additional information about the parameters needed
|
||||
/// for a particular scheme. But I think that's a change we can make later
|
||||
/// once we have a scheme that takes parameters.
|
||||
///
|
||||
/// TODO RPC: Should we indicate which schemes get you additional privileges?
|
||||
schemes: Vec<AuthenticationScheme>,
|
||||
}
|
||||
|
||||
rpc::decl_method! {"auth:query" => AuthQuery}
|
||||
impl rpc::Method for AuthQuery {
|
||||
type Output = SupportedAuth;
|
||||
type Update = rpc::NoUpdates;
|
||||
}
|
||||
/// Implement `auth:AuthQuery` on a connection.
|
||||
async fn conn_authquery(
|
||||
_conn: Arc<Connection>,
|
||||
_query: Box<AuthQuery>,
|
||||
_ctx: Box<dyn rpc::Context>,
|
||||
) -> Result<SupportedAuth, rpc::RpcError> {
|
||||
// Right now, every connection supports the same scheme.
|
||||
Ok(SupportedAuth {
|
||||
schemes: vec![AuthenticationScheme::InherentUnixPath],
|
||||
})
|
||||
}
|
||||
rpc::rpc_invoke_fn! {
|
||||
conn_authquery(Connection, AuthQuery);
|
||||
}
|
||||
|
||||
/// Method to implement basic authentication. Right now only "I connected to
|
||||
/// you so I must have permission!" is supported.
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct Authenticate {
|
||||
/// The authentication scheme as enumerated in the spec.
|
||||
///
|
||||
/// TODO RPC: The only supported one for now is "inherent:unix_path"
|
||||
scheme: AuthenticationScheme,
|
||||
}
|
||||
|
||||
/// A reply from the `Authenticate` method.
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
struct AuthenticateReply {
|
||||
/// An owned reference to a `Session` object.
|
||||
session: rpc::ObjectId,
|
||||
}
|
||||
|
||||
rpc::decl_method! {"auth:authenticate" => Authenticate}
|
||||
impl rpc::Method for Authenticate {
|
||||
type Output = AuthenticateReply;
|
||||
type Update = rpc::NoUpdates;
|
||||
}
|
||||
|
||||
/// An error during authentication.
|
||||
#[derive(Debug, Clone, thiserror::Error, serde::Serialize)]
|
||||
enum AuthenticationFailure {}
|
||||
|
||||
impl tor_error::HasKind for AuthenticationFailure {
|
||||
fn kind(&self) -> tor_error::ErrorKind {
|
||||
// TODO RPC not right.
|
||||
tor_error::ErrorKind::LocalProtocolViolation
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoke the "authenticate" method on a connection.
|
||||
///
|
||||
/// TODO RPC: This behavior is wrong; we'll need to fix it to be all
|
||||
/// capabilities-like.
|
||||
async fn authenticate_connection(
|
||||
unauth: Arc<Connection>,
|
||||
method: Box<Authenticate>,
|
||||
ctx: Box<dyn rpc::Context>,
|
||||
) -> Result<AuthenticateReply, rpc::RpcError> {
|
||||
match method.scheme {
|
||||
// For now, we only support AF_UNIX connections, and we assume that if
|
||||
// you have permission to open such a connection to us, you have
|
||||
// permission to use Arti. We will refine this later on!
|
||||
AuthenticationScheme::InherentUnixPath => {}
|
||||
}
|
||||
|
||||
// TODO RPC: I'm actually not totally sure about the semantics of creating a
|
||||
// new session object here, since it will _look_ separate from other
|
||||
// sessions, but in fact they will all share the same object map.
|
||||
//
|
||||
// Perhaps we need to think more about the semantics of authenticating more
|
||||
// then once on the same connection.
|
||||
let client = unauth.inner.lock().expect("lock poisoned").client.clone();
|
||||
let session = crate::session::Session::new(client);
|
||||
let session = ctx.register_owned(session);
|
||||
Ok(AuthenticateReply { session })
|
||||
}
|
||||
rpc::rpc_invoke_fn! {
|
||||
authenticate_connection(Connection, Authenticate);
|
||||
}
|
|
@ -43,6 +43,7 @@ mod err;
|
|||
mod mgr;
|
||||
mod msgs;
|
||||
mod objmap;
|
||||
mod session;
|
||||
mod streams;
|
||||
|
||||
pub use connection::{Connection, ConnectionError};
|
||||
|
|
|
@ -181,6 +181,11 @@ impl TaggedAddr {
|
|||
/// analyze these object IDs, please contact the Arti developers instead and let
|
||||
/// us give you a better way to do whatever you want.
|
||||
impl GenIdx {
|
||||
/// Return true if this is a strong (owning) reference.
|
||||
pub(crate) fn is_strong(&self) -> bool {
|
||||
matches!(self, GenIdx::Strong(_))
|
||||
}
|
||||
|
||||
/// Encode `self` into an rpc::ObjectId that we can give to a client.
|
||||
pub(crate) fn encode(self) -> rpc::ObjectId {
|
||||
self.encode_with_rng(&mut rand::thread_rng())
|
||||
|
@ -319,18 +324,19 @@ impl ObjMap {
|
|||
}
|
||||
}
|
||||
|
||||
/// Remove the entry at `idx`, if any.
|
||||
pub(crate) fn remove(&mut self, idx: GenIdx) {
|
||||
/// Remove and return the entry at `idx`, if any.
|
||||
pub(crate) fn remove(&mut self, idx: GenIdx) -> Option<Arc<dyn rpc::Object>> {
|
||||
match idx {
|
||||
GenIdx::Weak(idx) => {
|
||||
if let Some(entry) = self.weak_arena.remove(idx) {
|
||||
let old_idx = self.reverse_map.remove(&entry.tagged_addr());
|
||||
debug_assert_eq!(old_idx, Some(idx));
|
||||
entry.obj.upgrade()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
GenIdx::Strong(idx) => {
|
||||
self.strong_arena.remove(idx);
|
||||
}
|
||||
GenIdx::Strong(idx) => self.strong_arena.remove(idx),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
//! High-level APIs for an RPC session
|
||||
//!
|
||||
//! A "session" is created when a user authenticates on an RPC connection. It
|
||||
//! is the root for all other RPC capabilities.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use tor_rpcbase as rpc;
|
||||
|
||||
/// An authenticated RPC session.
|
||||
pub(crate) struct Session {
|
||||
/// An inner TorClient object that we use to implement remaining
|
||||
/// functionality.
|
||||
#[allow(unused)]
|
||||
client: Arc<dyn rpc::Object>,
|
||||
}
|
||||
impl rpc::Object for Session {}
|
||||
rpc::decl_object! {Session}
|
||||
|
||||
impl Session {
|
||||
/// Create a new session object.
|
||||
pub(crate) fn new(client: Arc<dyn rpc::Object>) -> Arc<Self> {
|
||||
Arc::new(Self { client })
|
||||
}
|
||||
}
|
||||
|
||||
/// RPC method to release a single strong reference.
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct RpcRelease {
|
||||
/// The object to release. Must be a strong reference.
|
||||
///
|
||||
/// TODO RPC: Releasing a weak reference is perilous and hard-to-define
|
||||
/// based on how we have implemented our object ids. If you tell the objmap
|
||||
/// to "release" a single name for a weak reference, you are releasing every
|
||||
/// name for that weak reference, which may have surprising results.
|
||||
///
|
||||
/// This might be a sign of a design problem.
|
||||
obj: rpc::ObjectId,
|
||||
}
|
||||
/// RPC method to release a single strong reference, creating a weak reference
|
||||
/// in its place.
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
struct RpcDowngrade {
|
||||
/// The object to downgrade
|
||||
obj: rpc::ObjectId,
|
||||
}
|
||||
|
||||
rpc::decl_method! { "rpc:release" => RpcRelease}
|
||||
impl rpc::Method for RpcRelease {
|
||||
type Output = rpc::Nil;
|
||||
type Update = rpc::NoUpdates;
|
||||
}
|
||||
|
||||
/// Implementation for calling "release" on a Session.
|
||||
async fn rpc_release(
|
||||
_obj: Arc<Session>,
|
||||
method: Box<RpcRelease>,
|
||||
ctx: Box<dyn rpc::Context>,
|
||||
) -> Result<rpc::Nil, rpc::RpcError> {
|
||||
ctx.release_owned(&method.obj)?;
|
||||
Ok(rpc::Nil::default())
|
||||
}
|
||||
rpc::rpc_invoke_fn! {
|
||||
rpc_release(Session,RpcRelease);
|
||||
}
|
||||
|
||||
/// A simple temporary method to echo a reply.
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
struct Echo {
|
||||
/// A message to echo.
|
||||
msg: String,
|
||||
}
|
||||
rpc::decl_method! { "arti:x-echo" => Echo}
|
||||
impl rpc::Method for Echo {
|
||||
type Output = Echo;
|
||||
type Update = rpc::NoUpdates;
|
||||
}
|
||||
|
||||
/// Implementation for calling "echo" on a Session.
|
||||
///
|
||||
/// TODO RPC: Remove this. It shouldn't exist.
|
||||
async fn echo_on_session(
|
||||
_obj: Arc<Session>,
|
||||
method: Box<Echo>,
|
||||
_ctx: Box<dyn rpc::Context>,
|
||||
) -> Result<Echo, rpc::RpcError> {
|
||||
Ok(*method)
|
||||
}
|
||||
|
||||
rpc::rpc_invoke_fn! {
|
||||
echo_on_session(Session,Echo);
|
||||
}
|
|
@ -434,6 +434,10 @@ mod test {
|
|||
fn register_weak(&self, _object: Arc<dyn crate::Object>) -> crate::ObjectId {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn release_owned(&self, _object: &crate::ObjectId) -> Result<(), crate::LookupError> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
|
|
|
@ -80,9 +80,7 @@ pub trait Context: Send {
|
|||
/// Look up an object by identity within this context.
|
||||
fn lookup_object(&self, id: &ObjectId) -> Result<Arc<dyn Object>, LookupError>;
|
||||
|
||||
/// Find an owning reference to `object` within this context. If none can
|
||||
/// be found, or if only a non-owning reference is found, make sure that
|
||||
/// this context contains an owning reference to `object`.
|
||||
/// Create an owning reference to `object` within this context.
|
||||
///
|
||||
/// Return an ObjectId for this object.
|
||||
///
|
||||
|
@ -90,9 +88,9 @@ pub trait Context: Send {
|
|||
/// function depending on how we decide to name and specify things.
|
||||
fn register_owned(&self, object: Arc<dyn Object>) -> ObjectId;
|
||||
|
||||
/// Find any owning reference to `object` within this context. If none can
|
||||
/// be found, make sure that
|
||||
/// this context contains a non-owning reference to `object`.
|
||||
/// Make sure that
|
||||
/// this context contains a non-owning reference to `object`,
|
||||
/// creating one if necessary.
|
||||
///
|
||||
/// Return an ObjectId for this object.
|
||||
///
|
||||
|
@ -102,6 +100,13 @@ pub trait Context: Send {
|
|||
/// TODO RPC: We may need to change the above semantics and the name of this
|
||||
/// function depending on how we decide to name and specify things.
|
||||
fn register_weak(&self, object: Arc<dyn Object>) -> ObjectId;
|
||||
|
||||
/// Drop an owning reference to the object called `object` within this context.
|
||||
///
|
||||
/// This will return an error if `object` is not an owning reference.
|
||||
///
|
||||
/// TODO RPC should this really return a LookupError?
|
||||
fn release_owned(&self, object: &ObjectId) -> Result<(), LookupError>;
|
||||
}
|
||||
|
||||
/// An error caused while trying to send an update to a method.
|
||||
|
@ -148,3 +153,13 @@ pub trait ContextExt: Context {
|
|||
}
|
||||
}
|
||||
impl<T: Context> ContextExt for T {}
|
||||
|
||||
/// A serializable empty object.
|
||||
///
|
||||
/// Used when we need to declare that a method returns nothing.
|
||||
///
|
||||
/// TODO RPC: Perhaps we can get () to serialize as {} and make this an alias
|
||||
/// for ().
|
||||
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, Default)]
|
||||
#[non_exhaustive]
|
||||
pub struct Nil {}
|
||||
|
|
|
@ -507,7 +507,7 @@ in order to receive any other object IDs.
|
|||
|
||||
The pre-authentication methods available on a connection are:
|
||||
|
||||
auth:get_proto
|
||||
auth:get_rpc_protocol
|
||||
: Ask Arti which version of the protocol is in use.
|
||||
|
||||
auth:query
|
||||
|
@ -776,9 +776,13 @@ The echo command will only work post-authentication.
|
|||
Here is an example session:
|
||||
|
||||
```
|
||||
>>> {"id": "abc", "obj": "connection", "method": "auth:get_rpc_protocol", "params": {}}
|
||||
<<< {"id":"abc","result":{"version":"alpha"}}
|
||||
>>> {"id": "abc", "obj": "connection", "method": "auth:query", "params": {}}
|
||||
<<< {"id":"abc","result":{"schemes":["inherent:unix_path"]}}
|
||||
>>> {"id": 3, "obj": "connection", "method": "auth:authenticate", "params": {"scheme": "inherent:unix_path"}}
|
||||
<<< {"id":3,"result":{"client":"dTewFIaZKQV1N7AUhpkpBIrIT-t5Ztb8"}}
|
||||
>>> {"id": 4, "obj": "dTewFIaZKQV1N7AUhpkpBIrIT-t5Ztb8", "method": "arti:x-echo", "params": {"msg": "Hello World"}}
|
||||
<<< {"id":3,"result":{"session":"2yFi5qrMD9LbIWLmqswP0iTenRlVM_Au"}}
|
||||
>>> {"id": 4, "obj": "2yFi5qrMD9LbIWLmqswP0iTenRlVM_Au", "method": "arti:x-echo", "params": {"msg": "Hello World"}}
|
||||
<<< {"id":4,"result":{"msg":"Hello World"}}
|
||||
```
|
||||
|
||||
|
|
Loading…
Reference in New Issue