diff --git a/CHANGELOG.md b/CHANGELOG.md index 80d15bed3..6ef247ab4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,7 +29,8 @@ All user visible changes to this project will be documented in this file. This p - gRPC Control API callbacks: - `on_join` ([#63], [#153]); - `on_leave` ([#63]). - - Configuration of `Member`'s Client API RPC settings ([#95]). + - Configuration of `Member`'s Client API RPC settings ([#95]); + - Hashed `Member` credentials support ([#168]). - Signalling: - Dynamic `Peer`s creation when client connects ([#28]); - Auto-removing `Peer`s when `Member` disconnects ([#28]); @@ -87,6 +88,7 @@ All user visible changes to this project will be documented in this file. This p [#153]: /../../pull/153 [#155]: /../../pull/155 [#156]: /../../pull/156 +[#168]: /../../pull/168 diff --git a/Cargo.lock b/Cargo.lock index 9fbddee85..6276166f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1757,6 +1757,7 @@ dependencies = [ "mockall", "rand 0.8.0", "redis", + "rust-argon2", "rust-crypto", "serde 1.0.118", "serde_json", @@ -1769,6 +1770,7 @@ dependencies = [ "slog-scope", "slog-stdlog", "smart-default", + "subtle", "tempfile", "tokio", "toml", @@ -3005,6 +3007,12 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" +[[package]] +name = "subtle" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2" + [[package]] name = "syn" version = "0.15.44" diff --git a/Cargo.toml b/Cargo.toml index 1f6e2f05f..65fc001ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ actix = "0.10" actix-http = "2.0" actix-web = "3.0" actix-web-actors = "3.0" +argon2 = { version = "0.8", package = "rust-argon2", default-features = false } async-trait = "0.1" bytes = "0.5" chrono = "0.4" @@ -57,6 +58,7 @@ slog-json = "2.3" slog-scope = "4.3" slog-stdlog = "4.0" smart-default = "0.6" +subtle = { version = "2.2", default-features = false } tokio = { version = "0.2", features = ["signal", "time"] } toml = "0.5" tonic = "0.3" diff --git a/_dev/specs/all-media-types-required-call.yml b/_dev/specs/all-media-types-required-call.yml index 2f4f94f9b..ad7aa4bfa 100644 --- a/_dev/specs/all-media-types-required-call.yml +++ b/_dev/specs/all-media-types-required-call.yml @@ -4,7 +4,8 @@ spec: pipeline: caller: kind: Member - credentials: test + credentials: + plain: test spec: pipeline: publish: @@ -21,7 +22,8 @@ spec: src: "local://all-media-types-required-call/responder/publish" responder: kind: Member - credentials: test + credentials: + plain: test spec: pipeline: publish: diff --git a/_dev/specs/audio-call.yml b/_dev/specs/audio-call.yml index 99858dc6a..4689310a8 100644 --- a/_dev/specs/audio-call.yml +++ b/_dev/specs/audio-call.yml @@ -4,7 +4,8 @@ spec: pipeline: member-1: kind: Member - credentials: test + credentials: + plain: test on_join: "grpc://127.0.0.1:9099" on_leave: "grpc://127.0.0.1:9099" idle_timeout: 1m @@ -24,7 +25,8 @@ spec: src: "local://audio-call/member-2/publish" member-2: kind: Member - credentials: test + credentials: + plain: test on_join: "grpc://127.0.0.1:9099" on_leave: "grpc://127.0.0.1:9099" idle_timeout: 1m diff --git a/_dev/specs/pub-pub-video-call.yml b/_dev/specs/pub-pub-video-call.yml index 63da76706..1312b867b 100644 --- a/_dev/specs/pub-pub-video-call.yml +++ b/_dev/specs/pub-pub-video-call.yml @@ -5,7 +5,8 @@ spec: # Here we're defining a member who initiates video call. caller: kind: Member - credentials: test + credentials: + plain: test on_join: "grpc://127.0.0.1:9099" on_leave: "grpc://127.0.0.1:9099" idle_timeout: 1m @@ -28,7 +29,8 @@ spec: src: "local://pub-pub-video-call/responder/publish" responder: kind: Member - credentials: test + credentials: + plain: test on_join: "grpc://127.0.0.1:9099" on_leave: "grpc://127.0.0.1:9099" idle_timeout: 1m diff --git a/_dev/specs/pub-sub-video-call.yml b/_dev/specs/pub-sub-video-call.yml index ec9442203..f04c5c57f 100644 --- a/_dev/specs/pub-sub-video-call.yml +++ b/_dev/specs/pub-sub-video-call.yml @@ -5,7 +5,8 @@ spec: # Here we're defining a member who initiates video call. publisher: kind: Member - credentials: test + credentials: + plain: test on_join: "grpc://127.0.0.1:9099" on_leave: "grpc://127.0.0.1:9099" spec: @@ -19,7 +20,8 @@ spec: p2p: Always subscriber: kind: Member - credentials: test + credentials: + plain: test on_join: "grpc://127.0.0.1:9099" on_leave: "grpc://127.0.0.1:9099" spec: diff --git a/_dev/specs/relay-pub-pub-video-call.yml b/_dev/specs/relay-pub-pub-video-call.yml index b4d8b33ee..c64e5b27f 100644 --- a/_dev/specs/relay-pub-pub-video-call.yml +++ b/_dev/specs/relay-pub-pub-video-call.yml @@ -4,7 +4,8 @@ spec: pipeline: caller: kind: Member - credentials: test + credentials: + plain: test on_join: "grpc://127.0.0.1:9099" on_leave: "grpc://127.0.0.1:9099" spec: @@ -21,7 +22,8 @@ spec: force_relay: true responder: kind: Member - credentials: test + credentials: + plain: test on_join: "grpc://127.0.0.1:9099" on_leave: "grpc://127.0.0.1:9099" spec: diff --git a/_dev/specs/three-members-conference.yml b/_dev/specs/three-members-conference.yml index 3ed460b21..63aa1d3a6 100644 --- a/_dev/specs/three-members-conference.yml +++ b/_dev/specs/three-members-conference.yml @@ -5,7 +5,8 @@ spec: # Here we're defining a member who initiates video call. caller: kind: Member - credentials: test + credentials: + plain: test spec: pipeline: # Media element which is able to receive media data from client @@ -27,7 +28,8 @@ spec: src: "local://three-members-conference/responder-2/publish" responder-1: kind: Member - credentials: test + credentials: + plain: test spec: pipeline: publish: @@ -44,7 +46,8 @@ spec: src: "local://three-members-conference/responder-2/publish" responder-2: kind: Member - credentials: test + credentials: + plain: test spec: pipeline: publish: diff --git a/_dev/specs/video-call-1.yml b/_dev/specs/video-call-1.yml index 82304e3e9..525453241 100644 --- a/_dev/specs/video-call-1.yml +++ b/_dev/specs/video-call-1.yml @@ -5,7 +5,8 @@ spec: # Here we're defining a member who initiates video call. caller: kind: Member - credentials: test + credentials: + plain: test spec: pipeline: # Media element which is able to receive media data from client @@ -23,7 +24,8 @@ spec: src: "local://video-call-1/responder/publish" responder: kind: Member - credentials: test + credentials: + plain: test spec: pipeline: publish: diff --git a/jason/demo/index.html b/jason/demo/index.html index 3558d9b86..4128b7a0b 100644 --- a/jason/demo/index.html +++ b/jason/demo/index.html @@ -184,7 +184,7 @@ url: controlUrl + roomId + '/' + memberId, data: { kind: 'Member', - credentials: 'test', + credentials: { plain: 'test' }, pipeline: pipeline, }, }); diff --git a/jason/e2e-demo/js/index.js b/jason/e2e-demo/js/index.js index 98d1f0672..1b9ad8002 100644 --- a/jason/e2e-demo/js/index.js +++ b/jason/e2e-demo/js/index.js @@ -65,7 +65,7 @@ async function createRoom(roomId, memberId) { pipeline: { [memberId]: { kind: 'Member', - credentials: 'test', + credentials: { plain: 'test' }, pipeline: pipeline, on_join: 'grpc://127.0.0.1:9099', on_leave: 'grpc://127.0.0.1:9099' @@ -130,7 +130,7 @@ async function createMember(roomId, memberId) { url: controlUrl + roomId + '/' + memberId, data: { kind: 'Member', - credentials: 'test', + credentials: { plain: 'test' }, pipeline: pipeline, on_join: 'grpc://127.0.0.1:9099', on_leave: 'grpc://127.0.0.1:9099' diff --git a/jason/src/api/room.rs b/jason/src/api/room.rs index e6d3d2525..5bb958c73 100644 --- a/jason/src/api/room.rs +++ b/jason/src/api/room.rs @@ -337,8 +337,12 @@ impl RoomHandle { .map(|inner| inner.on_connection_loss.set_func(f)) } - /// Performs entering to a [`Room`] with the preconfigured authorization - /// `token` for connection with media server. + /// Connects media server and enters [`Room`] with provided authorization + /// `token`. + /// + /// Authorization token has fixed format: + /// `{{ Host URL }}/{{ Room ID }}/{{ Member ID }}?token={{ Auth Token }}` + /// (e.g. `wss://medea.com/MyConf1/Alice?token=777`). /// /// Establishes connection with media server (if it doesn't already exist). /// Fails if: diff --git a/jason/src/rpc/mod.rs b/jason/src/rpc/mod.rs index 61336791c..c99b45ec3 100644 --- a/jason/src/rpc/mod.rs +++ b/jason/src/rpc/mod.rs @@ -88,6 +88,10 @@ pub enum ConnectionInfoParseError { /// Provided URL doesn't have important segments. #[display(fmt = "Provided URL doesn't have important segments")] NotEnoughSegments, + + /// Provided URL doesn't contain auth token. + #[display(fmt = "Provided URL does not contain auth token")] + NoToken, } impl FromStr for ConnectionInfo { @@ -98,6 +102,15 @@ impl FromStr for ConnectionInfo { let mut url = Url::parse(s).map_err(|err| tracerr::new!(E::UrlParse(err)))?; + + let credential = url + .query_pairs() + .find(|(key, _)| key.as_ref() == "token") + .ok_or_else(|| tracerr::new!(E::NoToken))? + .1 + .to_owned() + .into(); + url.set_fragment(None); url.set_query(None); @@ -105,11 +118,6 @@ impl FromStr for ConnectionInfo { .path_segments() .ok_or_else(|| tracerr::new!(E::NotEnoughSegments))? .rev(); - let credential = segments - .next() - .ok_or_else(|| tracerr::new!(E::NotEnoughSegments))? - .to_owned() - .into(); let member_id = segments .next() .ok_or_else(|| tracerr::new!(E::NotEnoughSegments))? @@ -121,9 +129,9 @@ impl FromStr for ConnectionInfo { .to_owned() .into(); - // Remove last three segments. Safe to unwrap cause we already made all + // Remove last two segments. Safe to unwrap cause we already made all // necessary checks. - url.path_segments_mut().unwrap().pop().pop().pop(); + url.path_segments_mut().unwrap().pop().pop(); Ok(ConnectionInfo { url: url.into(), diff --git a/jason/src/rpc/rpc_session.rs b/jason/src/rpc/rpc_session.rs index cdf9b6e01..56bb72ac8 100644 --- a/jason/src/rpc/rpc_session.rs +++ b/jason/src/rpc/rpc_session.rs @@ -40,7 +40,7 @@ pub enum SessionError { NoCredentials, /// [`WebSocketRpcSession`] authorization on the server was failed. - #[display(fmt = "RPC Session authorization on the server was failed")] + #[display(fmt = "Failed to authorize RPC session")] AuthorizationFailed, /// [`WebSocketRpcClient`] returned [`RpcClientError`]. diff --git a/jason/tests/api/mod.rs b/jason/tests/api/mod.rs index 3a6505c2d..12942f799 100644 --- a/jason/tests/api/mod.rs +++ b/jason/tests/api/mod.rs @@ -205,11 +205,9 @@ async fn room_dispose_works() { }); } }); - JsFuture::from( - another_room.join( - "ws://example.com/another_room_id/member_id/token".to_string(), - ), - ) + JsFuture::from(another_room.join( + "ws://example.com/another_room_id/member_id?token=token".to_string(), + )) .await .unwrap(); diff --git a/jason/tests/rpc/rpc_session.rs b/jason/tests/rpc/rpc_session.rs index b1dc889ad..ed6e67669 100644 --- a/jason/tests/rpc/rpc_session.rs +++ b/jason/tests/rpc/rpc_session.rs @@ -160,8 +160,10 @@ async fn could_not_open_transport() { let mut on_connection_loss = session.on_connection_loss().fuse(); let connect_fut = Rc::clone(&session).connect( - ConnectionInfo::from_str("ws://localhost:55555/some/fake/endpoint") - .unwrap(), + ConnectionInfo::from_str( + "ws://localhost:55555/some/fake?token=endpoint", + ) + .unwrap(), ); // connect resolve with err diff --git a/jason/tests/web.rs b/jason/tests/web.rs index ea4d9078d..a83e0a4f2 100644 --- a/jason/tests/web.rs +++ b/jason/tests/web.rs @@ -235,7 +235,7 @@ pub fn get_test_recv_tracks() -> (Track, Track) { ) } -const TEST_ROOM_URL: &str = "ws://example.com/room_id/member_id/token"; +const TEST_ROOM_URL: &str = "ws://example.com/room_id/member_id?token=token"; pub fn join_room_url() -> ApiUrl { Url::parse("ws://example.com/ws").unwrap().into() diff --git a/mock/control-api/src/api/member.rs b/mock/control-api/src/api/member.rs index d69d6084c..0fd0020b1 100644 --- a/mock/control-api/src/api/member.rs +++ b/mock/control-api/src/api/member.rs @@ -24,7 +24,7 @@ pub struct Member { /// Optional `Member` credentials. /// /// If `None` then random credentials will be generated on Medea side. - credentials: Option, + credentials: Option, /// URL to which `OnJoin` Control API callback will be sent. #[serde(skip_serializing_if = "Option::is_none")] @@ -62,7 +62,7 @@ impl Member { proto::Member { pipeline: member_elements, id, - credentials: self.credentials.unwrap_or_default(), + credentials: self.credentials.map(Into::into), on_join: self.on_join.unwrap_or_default(), on_leave: self.on_leave.unwrap_or_default(), idle_timeout: self.idle_timeout.map(Into::into), @@ -91,7 +91,7 @@ impl From for Member { Self { id: proto.id, pipeline: member_pipeline, - credentials: Some(proto.credentials), + credentials: proto.credentials.map(Into::into), on_join: Some(proto.on_join).filter(|s| !s.is_empty()), on_leave: Some(proto.on_leave).filter(|s| !s.is_empty()), idle_timeout: proto.idle_timeout.map(|dur| dur.try_into().unwrap()), @@ -104,3 +104,38 @@ impl From for Member { } } } + +/// Credentials of the [`Member`]. +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "lowercase")] +enum Credentials { + /// [Argon2] hash of the [`Member`] credentials. + /// + /// [Argon2]: https://en.wikipedia.org/wiki/Argon2 + Hash(String), + + /// Plain text [`Member`] credentials. + Plain(String), +} + +impl From for Credentials { + #[inline] + fn from(from: proto::member::Credentials) -> Self { + use proto::member::Credentials as C; + match from { + C::Plain(plain) => Self::Plain(plain), + C::Hash(hash) => Self::Hash(hash), + } + } +} + +impl From for proto::member::Credentials { + #[inline] + fn from(from: Credentials) -> Self { + use Credentials as C; + match from { + C::Hash(hash) => Self::Hash(hash), + C::Plain(plain) => Self::Plain(plain), + } + } +} diff --git a/proto/control-api/src/grpc/api.proto b/proto/control-api/src/grpc/api.proto index 288840d05..374d7f5d5 100644 --- a/proto/control-api/src/grpc/api.proto +++ b/proto/control-api/src/grpc/api.proto @@ -141,17 +141,30 @@ message Member { // with a media server via Client API. string on_leave = 3; // Credentials of the Member to authorize via Client API with. - string credentials = 4; + // + // Plain and hashed credentials are supported. If no credentials provided, + // then random plain string will be generated. If no authentication is + // required then empty plain string can be used. + // + // Hashed variant only supports Argon2 hash at the moment. + // Member sid won't contain token if hashed credentials are used, so token + // query parameter should be appended manually. + oneof credentials { + // Argon2 hash of credentials. + string hash = 4; + // Plain text credentials. + string plain = 5; + } // Timeout of receiving heartbeat messages from the Member via Client API. // Once reached, the Member is considered being idle. - google.protobuf.Duration idle_timeout = 5; + google.protobuf.Duration idle_timeout = 6; // Timeout of the Member reconnecting via Client API. // Once reached, the Member is considered disconnected. - google.protobuf.Duration reconnect_timeout = 6; + google.protobuf.Duration reconnect_timeout = 7; // Interval of sending pings from a media server to the Member via Client API. - google.protobuf.Duration ping_interval = 7; + google.protobuf.Duration ping_interval = 8; // Pipeline of this Member. - map pipeline = 8; + map pipeline = 9; // Elements which Member's pipeline can contain. message Element { diff --git a/proto/control-api/src/grpc/api.rs b/proto/control-api/src/grpc/api.rs index 6ad48faab..8bdeb98e9 100644 --- a/proto/control-api/src/grpc/api.rs +++ b/proto/control-api/src/grpc/api.rs @@ -159,23 +159,31 @@ pub struct Member { /// with a media server via Client API. #[prost(string, tag="3")] pub on_leave: std::string::String, - /// Credentials of the Member to authorize via Client API with. - #[prost(string, tag="4")] - pub credentials: std::string::String, /// Timeout of receiving heartbeat messages from the Member via Client API. /// Once reached, the Member is considered being idle. - #[prost(message, optional, tag="5")] + #[prost(message, optional, tag="6")] pub idle_timeout: ::std::option::Option<::prost_types::Duration>, /// Timeout of the Member reconnecting via Client API. /// Once reached, the Member is considered disconnected. - #[prost(message, optional, tag="6")] + #[prost(message, optional, tag="7")] pub reconnect_timeout: ::std::option::Option<::prost_types::Duration>, /// Interval of sending pings from a media server to the Member via Client API. - #[prost(message, optional, tag="7")] + #[prost(message, optional, tag="8")] pub ping_interval: ::std::option::Option<::prost_types::Duration>, /// Pipeline of this Member. - #[prost(map="string, message", tag="8")] + #[prost(map="string, message", tag="9")] pub pipeline: ::std::collections::HashMap, + /// Credentials of the Member to authorize via Client API with. + /// + /// Plain and hashed credentials are supported. If no credentials provided, + /// then random plain string will be generated. If no authentication is + /// required then empty plain string can be used. + /// + /// Hashed variant only supports Argon2 hash at the moment. + /// Member sid won't contain token if hashed credentials are used, so token + /// query parameter should be appended manually. + #[prost(oneof="member::Credentials", tags="4, 5")] + pub credentials: ::std::option::Option, } pub mod member { /// Elements which Member's pipeline can contain. @@ -193,6 +201,24 @@ pub mod member { WebrtcPub(super::super::WebRtcPublishEndpoint), } } + /// Credentials of the Member to authorize via Client API with. + /// + /// Plain and hashed credentials are supported. If no credentials provided, + /// then random plain string will be generated. If no authentication is + /// required then empty plain string can be used. + /// + /// Hashed variant only supports Argon2 hash at the moment. + /// Member sid won't contain token if hashed credentials are used, so token + /// query parameter should be appended manually. + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Credentials { + /// Argon2 hash of credentials. + #[prost(string, tag="4")] + Hash(std::string::String), + /// Plain text credentials. + #[prost(string, tag="5")] + Plain(std::string::String), + } } /// Media element which is able to receive media data from a client via WebRTC /// (allows to publish media data). @@ -292,4 +318,4 @@ pub struct WebRtcPlayEndpoint { #[prost(bool, tag="5")] pub force_relay: bool, } -# [ doc = r" Generated client implementations." ] pub mod control_api_client { # ! [ allow ( unused_variables , dead_code , missing_docs ) ] use tonic :: codegen :: * ; # [ doc = " Media server's Control API service." ] pub struct ControlApiClient < T > { inner : tonic :: client :: Grpc < T > , } impl ControlApiClient < tonic :: transport :: Channel > { # [ doc = r" Attempt to create a new client by connecting to a given endpoint." ] pub async fn connect < D > ( dst : D ) -> Result < Self , tonic :: transport :: Error > where D : std :: convert :: TryInto < tonic :: transport :: Endpoint > , D :: Error : Into < StdError > , { let conn = tonic :: transport :: Endpoint :: new ( dst ) ? . connect ( ) . await ? ; Ok ( Self :: new ( conn ) ) } } impl < T > ControlApiClient < T > where T : tonic :: client :: GrpcService < tonic :: body :: BoxBody > , T :: ResponseBody : Body + HttpBody + Send + 'static , T :: Error : Into < StdError > , < T :: ResponseBody as HttpBody > :: Error : Into < StdError > + Send , { pub fn new ( inner : T ) -> Self { let inner = tonic :: client :: Grpc :: new ( inner ) ; Self { inner } } pub fn with_interceptor ( inner : T , interceptor : impl Into < tonic :: Interceptor > ) -> Self { let inner = tonic :: client :: Grpc :: with_interceptor ( inner , interceptor ) ; Self { inner } } # [ doc = " Creates new Element with a given ID." ] # [ doc = "" ] # [ doc = " Not idempotent. Errors if an Element with the same ID already exists." ] pub async fn create ( & mut self , request : impl tonic :: IntoRequest < super :: CreateRequest > , ) -> Result < tonic :: Response < super :: CreateResponse > , tonic :: Status > { self . inner . ready ( ) . await . map_err ( | e | { tonic :: Status :: new ( tonic :: Code :: Unknown , format ! ( "Service was not ready: {}" , e . into ( ) ) ) } ) ? ; let codec = tonic :: codec :: ProstCodec :: default ( ) ; let path = http :: uri :: PathAndQuery :: from_static ( "/api.ControlApi/Create" ) ; self . inner . unary ( request . into_request ( ) , path , codec ) . await } # [ doc = " Removes Element by its ID." ] # [ doc = " Allows referring multiple Elements on the last two levels." ] # [ doc = "" ] # [ doc = " Idempotent. If no Elements with such IDs exist, then succeeds." ] pub async fn delete ( & mut self , request : impl tonic :: IntoRequest < super :: IdRequest > , ) -> Result < tonic :: Response < super :: Response > , tonic :: Status > { self . inner . ready ( ) . await . map_err ( | e | { tonic :: Status :: new ( tonic :: Code :: Unknown , format ! ( "Service was not ready: {}" , e . into ( ) ) ) } ) ? ; let codec = tonic :: codec :: ProstCodec :: default ( ) ; let path = http :: uri :: PathAndQuery :: from_static ( "/api.ControlApi/Delete" ) ; self . inner . unary ( request . into_request ( ) , path , codec ) . await } # [ doc = " Returns Element by its ID." ] # [ doc = " Allows referring multiple Elements." ] # [ doc = " If no ID specified, returns all Elements declared." ] pub async fn get ( & mut self , request : impl tonic :: IntoRequest < super :: IdRequest > , ) -> Result < tonic :: Response < super :: GetResponse > , tonic :: Status > { self . inner . ready ( ) . await . map_err ( | e | { tonic :: Status :: new ( tonic :: Code :: Unknown , format ! ( "Service was not ready: {}" , e . into ( ) ) ) } ) ? ; let codec = tonic :: codec :: ProstCodec :: default ( ) ; let path = http :: uri :: PathAndQuery :: from_static ( "/api.ControlApi/Get" ) ; self . inner . unary ( request . into_request ( ) , path , codec ) . await } } impl < T : Clone > Clone for ControlApiClient < T > { fn clone ( & self ) -> Self { Self { inner : self . inner . clone ( ) , } } } impl < T > std :: fmt :: Debug for ControlApiClient < T > { fn fmt ( & self , f : & mut std :: fmt :: Formatter < '_ > ) -> std :: fmt :: Result { write ! ( f , "ControlApiClient {{ ... }}" ) } } }# [ doc = r" Generated server implementations." ] pub mod control_api_server { # ! [ allow ( unused_variables , dead_code , missing_docs ) ] use tonic :: codegen :: * ; # [ doc = "Generated trait containing gRPC methods that should be implemented for use with ControlApiServer." ] # [ async_trait ] pub trait ControlApi : Send + Sync + 'static { # [ doc = " Creates new Element with a given ID." ] # [ doc = "" ] # [ doc = " Not idempotent. Errors if an Element with the same ID already exists." ] async fn create ( & self , request : tonic :: Request < super :: CreateRequest > ) -> Result < tonic :: Response < super :: CreateResponse > , tonic :: Status > ; # [ doc = " Removes Element by its ID." ] # [ doc = " Allows referring multiple Elements on the last two levels." ] # [ doc = "" ] # [ doc = " Idempotent. If no Elements with such IDs exist, then succeeds." ] async fn delete ( & self , request : tonic :: Request < super :: IdRequest > ) -> Result < tonic :: Response < super :: Response > , tonic :: Status > ; # [ doc = " Returns Element by its ID." ] # [ doc = " Allows referring multiple Elements." ] # [ doc = " If no ID specified, returns all Elements declared." ] async fn get ( & self , request : tonic :: Request < super :: IdRequest > ) -> Result < tonic :: Response < super :: GetResponse > , tonic :: Status > ; } # [ doc = " Media server's Control API service." ] # [ derive ( Debug ) ] pub struct ControlApiServer < T : ControlApi > { inner : _Inner < T > , } struct _Inner < T > ( Arc < T > , Option < tonic :: Interceptor > ) ; impl < T : ControlApi > ControlApiServer < T > { pub fn new ( inner : T ) -> Self { let inner = Arc :: new ( inner ) ; let inner = _Inner ( inner , None ) ; Self { inner } } pub fn with_interceptor ( inner : T , interceptor : impl Into < tonic :: Interceptor > ) -> Self { let inner = Arc :: new ( inner ) ; let inner = _Inner ( inner , Some ( interceptor . into ( ) ) ) ; Self { inner } } } impl < T , B > Service < http :: Request < B >> for ControlApiServer < T > where T : ControlApi , B : HttpBody + Send + Sync + 'static , B :: Error : Into < StdError > + Send + 'static , { type Response = http :: Response < tonic :: body :: BoxBody > ; type Error = Never ; type Future = BoxFuture < Self :: Response , Self :: Error > ; fn poll_ready ( & mut self , _cx : & mut Context < '_ > ) -> Poll < Result < ( ) , Self :: Error >> { Poll :: Ready ( Ok ( ( ) ) ) } fn call ( & mut self , req : http :: Request < B > ) -> Self :: Future { let inner = self . inner . clone ( ) ; match req . uri ( ) . path ( ) { "/api.ControlApi/Create" => { # [ allow ( non_camel_case_types ) ] struct CreateSvc < T : ControlApi > ( pub Arc < T > ) ; impl < T : ControlApi > tonic :: server :: UnaryService < super :: CreateRequest > for CreateSvc < T > { type Response = super :: CreateResponse ; type Future = BoxFuture < tonic :: Response < Self :: Response > , tonic :: Status > ; fn call ( & mut self , request : tonic :: Request < super :: CreateRequest > ) -> Self :: Future { let inner = self . 0 . clone ( ) ; let fut = async move { ( * inner ) . create ( request ) . await } ; Box :: pin ( fut ) } } let inner = self . inner . clone ( ) ; let fut = async move { let interceptor = inner . 1 . clone ( ) ; let inner = inner . 0 ; let method = CreateSvc ( inner ) ; let codec = tonic :: codec :: ProstCodec :: default ( ) ; let mut grpc = if let Some ( interceptor ) = interceptor { tonic :: server :: Grpc :: with_interceptor ( codec , interceptor ) } else { tonic :: server :: Grpc :: new ( codec ) } ; let res = grpc . unary ( method , req ) . await ; Ok ( res ) } ; Box :: pin ( fut ) } "/api.ControlApi/Delete" => { # [ allow ( non_camel_case_types ) ] struct DeleteSvc < T : ControlApi > ( pub Arc < T > ) ; impl < T : ControlApi > tonic :: server :: UnaryService < super :: IdRequest > for DeleteSvc < T > { type Response = super :: Response ; type Future = BoxFuture < tonic :: Response < Self :: Response > , tonic :: Status > ; fn call ( & mut self , request : tonic :: Request < super :: IdRequest > ) -> Self :: Future { let inner = self . 0 . clone ( ) ; let fut = async move { ( * inner ) . delete ( request ) . await } ; Box :: pin ( fut ) } } let inner = self . inner . clone ( ) ; let fut = async move { let interceptor = inner . 1 . clone ( ) ; let inner = inner . 0 ; let method = DeleteSvc ( inner ) ; let codec = tonic :: codec :: ProstCodec :: default ( ) ; let mut grpc = if let Some ( interceptor ) = interceptor { tonic :: server :: Grpc :: with_interceptor ( codec , interceptor ) } else { tonic :: server :: Grpc :: new ( codec ) } ; let res = grpc . unary ( method , req ) . await ; Ok ( res ) } ; Box :: pin ( fut ) } "/api.ControlApi/Get" => { # [ allow ( non_camel_case_types ) ] struct GetSvc < T : ControlApi > ( pub Arc < T > ) ; impl < T : ControlApi > tonic :: server :: UnaryService < super :: IdRequest > for GetSvc < T > { type Response = super :: GetResponse ; type Future = BoxFuture < tonic :: Response < Self :: Response > , tonic :: Status > ; fn call ( & mut self , request : tonic :: Request < super :: IdRequest > ) -> Self :: Future { let inner = self . 0 . clone ( ) ; let fut = async move { ( * inner ) . get ( request ) . await } ; Box :: pin ( fut ) } } let inner = self . inner . clone ( ) ; let fut = async move { let interceptor = inner . 1 . clone ( ) ; let inner = inner . 0 ; let method = GetSvc ( inner ) ; let codec = tonic :: codec :: ProstCodec :: default ( ) ; let mut grpc = if let Some ( interceptor ) = interceptor { tonic :: server :: Grpc :: with_interceptor ( codec , interceptor ) } else { tonic :: server :: Grpc :: new ( codec ) } ; let res = grpc . unary ( method , req ) . await ; Ok ( res ) } ; Box :: pin ( fut ) } _ => Box :: pin ( async move { Ok ( http :: Response :: builder ( ) . status ( 200 ) . header ( "grpc-status" , "12" ) . body ( tonic :: body :: BoxBody :: empty ( ) ) . unwrap ( ) ) } ) , } } } impl < T : ControlApi > Clone for ControlApiServer < T > { fn clone ( & self ) -> Self { let inner = self . inner . clone ( ) ; Self { inner } } } impl < T : ControlApi > Clone for _Inner < T > { fn clone ( & self ) -> Self { Self ( self . 0 . clone ( ) , self . 1 . clone ( ) ) } } impl < T : std :: fmt :: Debug > std :: fmt :: Debug for _Inner < T > { fn fmt ( & self , f : & mut std :: fmt :: Formatter < '_ > ) -> std :: fmt :: Result { write ! ( f , "{:?}" , self . 0 ) } } impl < T : ControlApi > tonic :: transport :: NamedService for ControlApiServer < T > { const NAME : & 'static str = "api.ControlApi" ; } } \ No newline at end of file +# [doc = r" Generated client implementations."] pub mod control_api_client { # ! [allow (unused_variables , dead_code , missing_docs)] use tonic :: codegen :: * ; # [doc = " Media server's Control API service."] pub struct ControlApiClient < T > { inner : tonic :: client :: Grpc < T > , } impl ControlApiClient < tonic :: transport :: Channel > { # [doc = r" Attempt to create a new client by connecting to a given endpoint."] pub async fn connect < D > (dst : D) -> Result < Self , tonic :: transport :: Error > where D : std :: convert :: TryInto < tonic :: transport :: Endpoint > , D :: Error : Into < StdError > , { let conn = tonic :: transport :: Endpoint :: new (dst) ? . connect () . await ? ; Ok (Self :: new (conn)) } } impl < T > ControlApiClient < T > where T : tonic :: client :: GrpcService < tonic :: body :: BoxBody > , T :: ResponseBody : Body + HttpBody + Send + 'static , T :: Error : Into < StdError > , < T :: ResponseBody as HttpBody > :: Error : Into < StdError > + Send , { pub fn new (inner : T) -> Self { let inner = tonic :: client :: Grpc :: new (inner) ; Self { inner } } pub fn with_interceptor (inner : T , interceptor : impl Into < tonic :: Interceptor >) -> Self { let inner = tonic :: client :: Grpc :: with_interceptor (inner , interceptor) ; Self { inner } } # [doc = " Creates new Element with a given ID."] # [doc = ""] # [doc = " Not idempotent. Errors if an Element with the same ID already exists."] pub async fn create (& mut self , request : impl tonic :: IntoRequest < super :: CreateRequest > ,) -> Result < tonic :: Response < super :: CreateResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/api.ControlApi/Create") ; self . inner . unary (request . into_request () , path , codec) . await } # [doc = " Removes Element by its ID."] # [doc = " Allows referring multiple Elements on the last two levels."] # [doc = ""] # [doc = " Idempotent. If no Elements with such IDs exist, then succeeds."] pub async fn delete (& mut self , request : impl tonic :: IntoRequest < super :: IdRequest > ,) -> Result < tonic :: Response < super :: Response > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/api.ControlApi/Delete") ; self . inner . unary (request . into_request () , path , codec) . await } # [doc = " Returns Element by its ID."] # [doc = " Allows referring multiple Elements."] # [doc = " If no ID specified, returns all Elements declared."] pub async fn get (& mut self , request : impl tonic :: IntoRequest < super :: IdRequest > ,) -> Result < tonic :: Response < super :: GetResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/api.ControlApi/Get") ; self . inner . unary (request . into_request () , path , codec) . await } } impl < T : Clone > Clone for ControlApiClient < T > { fn clone (& self) -> Self { Self { inner : self . inner . clone () , } } } impl < T > std :: fmt :: Debug for ControlApiClient < T > { fn fmt (& self , f : & mut std :: fmt :: Formatter < '_ >) -> std :: fmt :: Result { write ! (f , "ControlApiClient {{ ... }}") } } }# [doc = r" Generated server implementations."] pub mod control_api_server { # ! [allow (unused_variables , dead_code , missing_docs)] use tonic :: codegen :: * ; # [doc = "Generated trait containing gRPC methods that should be implemented for use with ControlApiServer."] # [async_trait] pub trait ControlApi : Send + Sync + 'static { # [doc = " Creates new Element with a given ID."] # [doc = ""] # [doc = " Not idempotent. Errors if an Element with the same ID already exists."] async fn create (& self , request : tonic :: Request < super :: CreateRequest >) -> Result < tonic :: Response < super :: CreateResponse > , tonic :: Status > ; # [doc = " Removes Element by its ID."] # [doc = " Allows referring multiple Elements on the last two levels."] # [doc = ""] # [doc = " Idempotent. If no Elements with such IDs exist, then succeeds."] async fn delete (& self , request : tonic :: Request < super :: IdRequest >) -> Result < tonic :: Response < super :: Response > , tonic :: Status > ; # [doc = " Returns Element by its ID."] # [doc = " Allows referring multiple Elements."] # [doc = " If no ID specified, returns all Elements declared."] async fn get (& self , request : tonic :: Request < super :: IdRequest >) -> Result < tonic :: Response < super :: GetResponse > , tonic :: Status > ; } # [doc = " Media server's Control API service."] # [derive (Debug)] pub struct ControlApiServer < T : ControlApi > { inner : _Inner < T > , } struct _Inner < T > (Arc < T > , Option < tonic :: Interceptor >) ; impl < T : ControlApi > ControlApiServer < T > { pub fn new (inner : T) -> Self { let inner = Arc :: new (inner) ; let inner = _Inner (inner , None) ; Self { inner } } pub fn with_interceptor (inner : T , interceptor : impl Into < tonic :: Interceptor >) -> Self { let inner = Arc :: new (inner) ; let inner = _Inner (inner , Some (interceptor . into ())) ; Self { inner } } } impl < T , B > Service < http :: Request < B >> for ControlApiServer < T > where T : ControlApi , B : HttpBody + Send + Sync + 'static , B :: Error : Into < StdError > + Send + 'static , { type Response = http :: Response < tonic :: body :: BoxBody > ; type Error = Never ; type Future = BoxFuture < Self :: Response , Self :: Error > ; fn poll_ready (& mut self , _cx : & mut Context < '_ >) -> Poll < Result < () , Self :: Error >> { Poll :: Ready (Ok (())) } fn call (& mut self , req : http :: Request < B >) -> Self :: Future { let inner = self . inner . clone () ; match req . uri () . path () { "/api.ControlApi/Create" => { # [allow (non_camel_case_types)] struct CreateSvc < T : ControlApi > (pub Arc < T >) ; impl < T : ControlApi > tonic :: server :: UnaryService < super :: CreateRequest > for CreateSvc < T > { type Response = super :: CreateResponse ; type Future = BoxFuture < tonic :: Response < Self :: Response > , tonic :: Status > ; fn call (& mut self , request : tonic :: Request < super :: CreateRequest >) -> Self :: Future { let inner = self . 0 . clone () ; let fut = async move { (* inner) . create (request) . await } ; Box :: pin (fut) } } let inner = self . inner . clone () ; let fut = async move { let interceptor = inner . 1 . clone () ; let inner = inner . 0 ; let method = CreateSvc (inner) ; let codec = tonic :: codec :: ProstCodec :: default () ; let mut grpc = if let Some (interceptor) = interceptor { tonic :: server :: Grpc :: with_interceptor (codec , interceptor) } else { tonic :: server :: Grpc :: new (codec) } ; let res = grpc . unary (method , req) . await ; Ok (res) } ; Box :: pin (fut) } "/api.ControlApi/Delete" => { # [allow (non_camel_case_types)] struct DeleteSvc < T : ControlApi > (pub Arc < T >) ; impl < T : ControlApi > tonic :: server :: UnaryService < super :: IdRequest > for DeleteSvc < T > { type Response = super :: Response ; type Future = BoxFuture < tonic :: Response < Self :: Response > , tonic :: Status > ; fn call (& mut self , request : tonic :: Request < super :: IdRequest >) -> Self :: Future { let inner = self . 0 . clone () ; let fut = async move { (* inner) . delete (request) . await } ; Box :: pin (fut) } } let inner = self . inner . clone () ; let fut = async move { let interceptor = inner . 1 . clone () ; let inner = inner . 0 ; let method = DeleteSvc (inner) ; let codec = tonic :: codec :: ProstCodec :: default () ; let mut grpc = if let Some (interceptor) = interceptor { tonic :: server :: Grpc :: with_interceptor (codec , interceptor) } else { tonic :: server :: Grpc :: new (codec) } ; let res = grpc . unary (method , req) . await ; Ok (res) } ; Box :: pin (fut) } "/api.ControlApi/Get" => { # [allow (non_camel_case_types)] struct GetSvc < T : ControlApi > (pub Arc < T >) ; impl < T : ControlApi > tonic :: server :: UnaryService < super :: IdRequest > for GetSvc < T > { type Response = super :: GetResponse ; type Future = BoxFuture < tonic :: Response < Self :: Response > , tonic :: Status > ; fn call (& mut self , request : tonic :: Request < super :: IdRequest >) -> Self :: Future { let inner = self . 0 . clone () ; let fut = async move { (* inner) . get (request) . await } ; Box :: pin (fut) } } let inner = self . inner . clone () ; let fut = async move { let interceptor = inner . 1 . clone () ; let inner = inner . 0 ; let method = GetSvc (inner) ; let codec = tonic :: codec :: ProstCodec :: default () ; let mut grpc = if let Some (interceptor) = interceptor { tonic :: server :: Grpc :: with_interceptor (codec , interceptor) } else { tonic :: server :: Grpc :: new (codec) } ; let res = grpc . unary (method , req) . await ; Ok (res) } ; Box :: pin (fut) } _ => Box :: pin (async move { Ok (http :: Response :: builder () . status (200) . header ("grpc-status" , "12") . body (tonic :: body :: BoxBody :: empty ()) . unwrap ()) }) , } } } impl < T : ControlApi > Clone for ControlApiServer < T > { fn clone (& self) -> Self { let inner = self . inner . clone () ; Self { inner } } } impl < T : ControlApi > Clone for _Inner < T > { fn clone (& self) -> Self { Self (self . 0 . clone () , self . 1 . clone ()) } } impl < T : std :: fmt :: Debug > std :: fmt :: Debug for _Inner < T > { fn fmt (& self , f : & mut std :: fmt :: Formatter < '_ >) -> std :: fmt :: Result { write ! (f , "{:?}" , self . 0) } } impl < T : ControlApi > tonic :: transport :: NamedService for ControlApiServer < T > { const NAME : & 'static str = "api.ControlApi" ; } } \ No newline at end of file diff --git a/proto/control-api/src/grpc/callback.rs b/proto/control-api/src/grpc/callback.rs index 61749518a..fc00ea7c3 100644 --- a/proto/control-api/src/grpc/callback.rs +++ b/proto/control-api/src/grpc/callback.rs @@ -51,4 +51,4 @@ pub mod on_leave { ServerShutdown = 2, } } -# [ doc = r" Generated client implementations." ] pub mod callback_client { # ! [ allow ( unused_variables , dead_code , missing_docs ) ] use tonic :: codegen :: * ; # [ doc = " Service for receiving callbacks from Medea media server." ] pub struct CallbackClient < T > { inner : tonic :: client :: Grpc < T > , } impl CallbackClient < tonic :: transport :: Channel > { # [ doc = r" Attempt to create a new client by connecting to a given endpoint." ] pub async fn connect < D > ( dst : D ) -> Result < Self , tonic :: transport :: Error > where D : std :: convert :: TryInto < tonic :: transport :: Endpoint > , D :: Error : Into < StdError > , { let conn = tonic :: transport :: Endpoint :: new ( dst ) ? . connect ( ) . await ? ; Ok ( Self :: new ( conn ) ) } } impl < T > CallbackClient < T > where T : tonic :: client :: GrpcService < tonic :: body :: BoxBody > , T :: ResponseBody : Body + HttpBody + Send + 'static , T :: Error : Into < StdError > , < T :: ResponseBody as HttpBody > :: Error : Into < StdError > + Send , { pub fn new ( inner : T ) -> Self { let inner = tonic :: client :: Grpc :: new ( inner ) ; Self { inner } } pub fn with_interceptor ( inner : T , interceptor : impl Into < tonic :: Interceptor > ) -> Self { let inner = tonic :: client :: Grpc :: with_interceptor ( inner , interceptor ) ; Self { inner } } # [ doc = "/ Fires when a certain callback event happens on Medea media server." ] pub async fn on_event ( & mut self , request : impl tonic :: IntoRequest < super :: Request > , ) -> Result < tonic :: Response < super :: Response > , tonic :: Status > { self . inner . ready ( ) . await . map_err ( | e | { tonic :: Status :: new ( tonic :: Code :: Unknown , format ! ( "Service was not ready: {}" , e . into ( ) ) ) } ) ? ; let codec = tonic :: codec :: ProstCodec :: default ( ) ; let path = http :: uri :: PathAndQuery :: from_static ( "/callback.Callback/OnEvent" ) ; self . inner . unary ( request . into_request ( ) , path , codec ) . await } } impl < T : Clone > Clone for CallbackClient < T > { fn clone ( & self ) -> Self { Self { inner : self . inner . clone ( ) , } } } impl < T > std :: fmt :: Debug for CallbackClient < T > { fn fmt ( & self , f : & mut std :: fmt :: Formatter < '_ > ) -> std :: fmt :: Result { write ! ( f , "CallbackClient {{ ... }}" ) } } }# [ doc = r" Generated server implementations." ] pub mod callback_server { # ! [ allow ( unused_variables , dead_code , missing_docs ) ] use tonic :: codegen :: * ; # [ doc = "Generated trait containing gRPC methods that should be implemented for use with CallbackServer." ] # [ async_trait ] pub trait Callback : Send + Sync + 'static { # [ doc = "/ Fires when a certain callback event happens on Medea media server." ] async fn on_event ( & self , request : tonic :: Request < super :: Request > ) -> Result < tonic :: Response < super :: Response > , tonic :: Status > ; } # [ doc = " Service for receiving callbacks from Medea media server." ] # [ derive ( Debug ) ] pub struct CallbackServer < T : Callback > { inner : _Inner < T > , } struct _Inner < T > ( Arc < T > , Option < tonic :: Interceptor > ) ; impl < T : Callback > CallbackServer < T > { pub fn new ( inner : T ) -> Self { let inner = Arc :: new ( inner ) ; let inner = _Inner ( inner , None ) ; Self { inner } } pub fn with_interceptor ( inner : T , interceptor : impl Into < tonic :: Interceptor > ) -> Self { let inner = Arc :: new ( inner ) ; let inner = _Inner ( inner , Some ( interceptor . into ( ) ) ) ; Self { inner } } } impl < T , B > Service < http :: Request < B >> for CallbackServer < T > where T : Callback , B : HttpBody + Send + Sync + 'static , B :: Error : Into < StdError > + Send + 'static , { type Response = http :: Response < tonic :: body :: BoxBody > ; type Error = Never ; type Future = BoxFuture < Self :: Response , Self :: Error > ; fn poll_ready ( & mut self , _cx : & mut Context < '_ > ) -> Poll < Result < ( ) , Self :: Error >> { Poll :: Ready ( Ok ( ( ) ) ) } fn call ( & mut self , req : http :: Request < B > ) -> Self :: Future { let inner = self . inner . clone ( ) ; match req . uri ( ) . path ( ) { "/callback.Callback/OnEvent" => { # [ allow ( non_camel_case_types ) ] struct OnEventSvc < T : Callback > ( pub Arc < T > ) ; impl < T : Callback > tonic :: server :: UnaryService < super :: Request > for OnEventSvc < T > { type Response = super :: Response ; type Future = BoxFuture < tonic :: Response < Self :: Response > , tonic :: Status > ; fn call ( & mut self , request : tonic :: Request < super :: Request > ) -> Self :: Future { let inner = self . 0 . clone ( ) ; let fut = async move { ( * inner ) . on_event ( request ) . await } ; Box :: pin ( fut ) } } let inner = self . inner . clone ( ) ; let fut = async move { let interceptor = inner . 1 . clone ( ) ; let inner = inner . 0 ; let method = OnEventSvc ( inner ) ; let codec = tonic :: codec :: ProstCodec :: default ( ) ; let mut grpc = if let Some ( interceptor ) = interceptor { tonic :: server :: Grpc :: with_interceptor ( codec , interceptor ) } else { tonic :: server :: Grpc :: new ( codec ) } ; let res = grpc . unary ( method , req ) . await ; Ok ( res ) } ; Box :: pin ( fut ) } _ => Box :: pin ( async move { Ok ( http :: Response :: builder ( ) . status ( 200 ) . header ( "grpc-status" , "12" ) . body ( tonic :: body :: BoxBody :: empty ( ) ) . unwrap ( ) ) } ) , } } } impl < T : Callback > Clone for CallbackServer < T > { fn clone ( & self ) -> Self { let inner = self . inner . clone ( ) ; Self { inner } } } impl < T : Callback > Clone for _Inner < T > { fn clone ( & self ) -> Self { Self ( self . 0 . clone ( ) , self . 1 . clone ( ) ) } } impl < T : std :: fmt :: Debug > std :: fmt :: Debug for _Inner < T > { fn fmt ( & self , f : & mut std :: fmt :: Formatter < '_ > ) -> std :: fmt :: Result { write ! ( f , "{:?}" , self . 0 ) } } impl < T : Callback > tonic :: transport :: NamedService for CallbackServer < T > { const NAME : & 'static str = "callback.Callback" ; } } \ No newline at end of file +# [doc = r" Generated client implementations."] pub mod callback_client { # ! [allow (unused_variables , dead_code , missing_docs)] use tonic :: codegen :: * ; # [doc = " Service for receiving callbacks from Medea media server."] pub struct CallbackClient < T > { inner : tonic :: client :: Grpc < T > , } impl CallbackClient < tonic :: transport :: Channel > { # [doc = r" Attempt to create a new client by connecting to a given endpoint."] pub async fn connect < D > (dst : D) -> Result < Self , tonic :: transport :: Error > where D : std :: convert :: TryInto < tonic :: transport :: Endpoint > , D :: Error : Into < StdError > , { let conn = tonic :: transport :: Endpoint :: new (dst) ? . connect () . await ? ; Ok (Self :: new (conn)) } } impl < T > CallbackClient < T > where T : tonic :: client :: GrpcService < tonic :: body :: BoxBody > , T :: ResponseBody : Body + HttpBody + Send + 'static , T :: Error : Into < StdError > , < T :: ResponseBody as HttpBody > :: Error : Into < StdError > + Send , { pub fn new (inner : T) -> Self { let inner = tonic :: client :: Grpc :: new (inner) ; Self { inner } } pub fn with_interceptor (inner : T , interceptor : impl Into < tonic :: Interceptor >) -> Self { let inner = tonic :: client :: Grpc :: with_interceptor (inner , interceptor) ; Self { inner } } # [doc = "/ Fires when a certain callback event happens on Medea media server."] pub async fn on_event (& mut self , request : impl tonic :: IntoRequest < super :: Request > ,) -> Result < tonic :: Response < super :: Response > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/callback.Callback/OnEvent") ; self . inner . unary (request . into_request () , path , codec) . await } } impl < T : Clone > Clone for CallbackClient < T > { fn clone (& self) -> Self { Self { inner : self . inner . clone () , } } } impl < T > std :: fmt :: Debug for CallbackClient < T > { fn fmt (& self , f : & mut std :: fmt :: Formatter < '_ >) -> std :: fmt :: Result { write ! (f , "CallbackClient {{ ... }}") } } }# [doc = r" Generated server implementations."] pub mod callback_server { # ! [allow (unused_variables , dead_code , missing_docs)] use tonic :: codegen :: * ; # [doc = "Generated trait containing gRPC methods that should be implemented for use with CallbackServer."] # [async_trait] pub trait Callback : Send + Sync + 'static { # [doc = "/ Fires when a certain callback event happens on Medea media server."] async fn on_event (& self , request : tonic :: Request < super :: Request >) -> Result < tonic :: Response < super :: Response > , tonic :: Status > ; } # [doc = " Service for receiving callbacks from Medea media server."] # [derive (Debug)] pub struct CallbackServer < T : Callback > { inner : _Inner < T > , } struct _Inner < T > (Arc < T > , Option < tonic :: Interceptor >) ; impl < T : Callback > CallbackServer < T > { pub fn new (inner : T) -> Self { let inner = Arc :: new (inner) ; let inner = _Inner (inner , None) ; Self { inner } } pub fn with_interceptor (inner : T , interceptor : impl Into < tonic :: Interceptor >) -> Self { let inner = Arc :: new (inner) ; let inner = _Inner (inner , Some (interceptor . into ())) ; Self { inner } } } impl < T , B > Service < http :: Request < B >> for CallbackServer < T > where T : Callback , B : HttpBody + Send + Sync + 'static , B :: Error : Into < StdError > + Send + 'static , { type Response = http :: Response < tonic :: body :: BoxBody > ; type Error = Never ; type Future = BoxFuture < Self :: Response , Self :: Error > ; fn poll_ready (& mut self , _cx : & mut Context < '_ >) -> Poll < Result < () , Self :: Error >> { Poll :: Ready (Ok (())) } fn call (& mut self , req : http :: Request < B >) -> Self :: Future { let inner = self . inner . clone () ; match req . uri () . path () { "/callback.Callback/OnEvent" => { # [allow (non_camel_case_types)] struct OnEventSvc < T : Callback > (pub Arc < T >) ; impl < T : Callback > tonic :: server :: UnaryService < super :: Request > for OnEventSvc < T > { type Response = super :: Response ; type Future = BoxFuture < tonic :: Response < Self :: Response > , tonic :: Status > ; fn call (& mut self , request : tonic :: Request < super :: Request >) -> Self :: Future { let inner = self . 0 . clone () ; let fut = async move { (* inner) . on_event (request) . await } ; Box :: pin (fut) } } let inner = self . inner . clone () ; let fut = async move { let interceptor = inner . 1 . clone () ; let inner = inner . 0 ; let method = OnEventSvc (inner) ; let codec = tonic :: codec :: ProstCodec :: default () ; let mut grpc = if let Some (interceptor) = interceptor { tonic :: server :: Grpc :: with_interceptor (codec , interceptor) } else { tonic :: server :: Grpc :: new (codec) } ; let res = grpc . unary (method , req) . await ; Ok (res) } ; Box :: pin (fut) } _ => Box :: pin (async move { Ok (http :: Response :: builder () . status (200) . header ("grpc-status" , "12") . body (tonic :: body :: BoxBody :: empty ()) . unwrap ()) }) , } } } impl < T : Callback > Clone for CallbackServer < T > { fn clone (& self) -> Self { let inner = self . inner . clone () ; Self { inner } } } impl < T : Callback > Clone for _Inner < T > { fn clone (& self) -> Self { Self (self . 0 . clone () , self . 1 . clone ()) } } impl < T : std :: fmt :: Debug > std :: fmt :: Debug for _Inner < T > { fn fmt (& self , f : & mut std :: fmt :: Formatter < '_ >) -> std :: fmt :: Result { write ! (f , "{:?}" , self . 0) } } impl < T : Callback > tonic :: transport :: NamedService for CallbackServer < T > { const NAME : & 'static str = "callback.Callback" ; } } \ No newline at end of file diff --git a/src/api/control/member.rs b/src/api/control/member.rs index 2183fb827..e03cca27c 100644 --- a/src/api/control/member.rs +++ b/src/api/control/member.rs @@ -8,7 +8,7 @@ use std::{ time::Duration, }; -use medea_client_api_proto::{Credential, MemberId as Id}; +use medea_client_api_proto::{self as client_proto, MemberId as Id}; use medea_control_api_proto::grpc::api as proto; use rand::{distributions::Alphanumeric, Rng}; use serde::Deserialize; @@ -25,7 +25,71 @@ use crate::api::control::{ WebRtcPlayId, }; -const CREDENTIALS_LEN: usize = 32; +/// Credentials of the `Member` element. +#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum Credential { + /// [Argon2] hash of the `Member` credential. + /// + /// [Argon2]: https://en.wikipedia.org/wiki/Argon2 + Hash(String), + + /// Plain text `Member` credentials. + Plain(String), +} + +impl Credential { + /// Length of [`Credential`]s. + const LEN: usize = 32; + + /// Verifies provided [`client_proto::Credential`]. + #[must_use] + pub fn verify(&self, other: &client_proto::Credential) -> bool { + use subtle::ConstantTimeEq as _; + match self { + Self::Hash(hash) => { + argon2::verify_encoded(hash, other.0.as_bytes()) + .unwrap_or(false) + } + Self::Plain(plain) => { + plain.as_bytes().ct_eq(other.0.as_bytes()).into() + } + } + } +} + +impl Default for Credential { + fn default() -> Self { + Self::Plain( + rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(Self::LEN) + .map(char::from) + .collect::(), + ) + } +} + +impl From for Credential { + #[inline] + fn from(from: proto::member::Credentials) -> Self { + use proto::member::Credentials as C; + match from { + C::Hash(hash) => Self::Hash(hash), + C::Plain(plain) => Self::Plain(plain), + } + } +} + +impl From for proto::member::Credentials { + #[inline] + fn from(from: Credential) -> Self { + match from { + Credential::Plain(plain) => Self::Plain(plain), + Credential::Hash(hash) => Self::Hash(hash), + } + } +} /// Element of [`Member`]'s [`Pipeline`]. /// @@ -186,24 +250,6 @@ impl MemberSpec { } } -/// Generates alphanumeric credentials for [`Member`] with -/// [`CREDENTIALS_LEN`] length. -/// -/// This credentials will be generated if in dynamic [Control API] spec not -/// provided credentials for [`Member`]. This logic you can find in [`TryFrom`] -/// [`proto::Member`] implemented for [`MemberSpec`]. -/// -/// [`Member`]: crate::signalling::elements::Member -/// [Control API]: https://tinyurl.com/yxsqplq7 -fn generate_member_credentials() -> Credential { - rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(CREDENTIALS_LEN) - .map(char::from) - .collect::() - .into() -} - impl TryFrom for MemberSpec { type Error = TryFromProtobufError; @@ -230,10 +276,9 @@ impl TryFrom for MemberSpec { } } - let mut credentials = Credential::from(member.credentials); - if credentials.0.is_empty() { - credentials = generate_member_credentials(); - } + let credentials = member + .credentials + .map_or_else(Credential::default, Credential::from); let on_leave = { let on_leave = member.on_leave; diff --git a/src/api/control/room.rs b/src/api/control/room.rs index 27119ef23..bc3e2d8ae 100644 --- a/src/api/control/room.rs +++ b/src/api/control/room.rs @@ -4,12 +4,13 @@ use std::{collections::HashMap, convert::TryFrom, time::Duration}; -use medea_client_api_proto::{Credential, MemberId, RoomId as Id}; +use medea_client_api_proto::{MemberId, RoomId as Id}; use medea_control_api_proto::grpc::api as proto; use serde::Deserialize; use crate::api::control::{ - callback::url::CallbackUrl, EndpointId, TryFromProtobufError, + callback::url::CallbackUrl, member::Credential, EndpointId, + TryFromProtobufError, }; use super::{ diff --git a/src/signalling/elements/member.rs b/src/signalling/elements/member.rs index 1916a4fd5..add1ab5c0 100644 --- a/src/signalling/elements/member.rs +++ b/src/signalling/elements/member.rs @@ -12,18 +12,19 @@ use std::{ use derive_more::Display; use failure::Fail; -use medea_client_api_proto::{Credential, MemberId, PeerId, RoomId}; +use medea_client_api_proto::{self as client_proto, MemberId, PeerId, RoomId}; use medea_control_api_proto::grpc::api as proto; use crate::{ api::control::{ callback::url::CallbackUrl, endpoints::WebRtcPlayEndpoint as WebRtcPlayEndpointSpec, + member::Credential, refs::{Fid, StatefulFid, ToEndpoint, ToMember, ToRoom}, EndpointId, MemberSpec, RoomSpec, TryFromElementError, WebRtcPlayId, WebRtcPublishId, }, - conf::Rpc as RpcConf, + conf, log::prelude::*, }; @@ -306,6 +307,16 @@ impl Member { self.0.borrow().credentials.clone() } + /// Verifies provided [`client_proto::Credential`]. + #[inline] + #[must_use] + pub fn verify_credentials( + &self, + credentials: &client_proto::Credential, + ) -> bool { + self.0.borrow().credentials.verify(&credentials) + } + /// Returns all srcs of this [`Member`]. pub fn srcs(&self) -> HashMap { self.0.borrow().srcs.clone() @@ -516,7 +527,7 @@ impl WeakMember { /// Errors with [`MembersLoadError`] if loading [`Member`] fails. pub fn parse_members( room_spec: &RoomSpec, - rpc_conf: RpcConf, + rpc_conf: conf::Rpc, ) -> Result, MembersLoadError> { let members_spec = room_spec.members().map_err(|e| { MembersLoadError::TryFromError( @@ -585,7 +596,7 @@ impl Into for Member { proto::Member { id: self.id().to_string(), - credentials: self.credentials().to_string(), + credentials: Some(self.credentials().into()), on_leave: self .get_on_leave() .map(|c| c.to_string()) @@ -633,7 +644,8 @@ mod tests { pipeline: caller: kind: Member - credentials: test + credentials: + plain: test spec: pipeline: publish: @@ -642,7 +654,8 @@ mod tests { p2p: Always some-member: kind: Member - credentials: test + credentials: + plain: test spec: pipeline: publish: @@ -651,7 +664,8 @@ mod tests { p2p: Always responder: kind: Member - credentials: test + credentials: + plain: test spec: pipeline: play: @@ -673,7 +687,7 @@ mod tests { let room_element: RootElement = serde_yaml::from_str(TEST_SPEC).unwrap(); let room_spec = RoomSpec::try_from(&room_element).unwrap(); - parse_members(&room_spec, RpcConf::default()).unwrap() + parse_members(&room_spec, conf::Rpc::default()).unwrap() } #[test] diff --git a/src/signalling/participants.rs b/src/signalling/participants.rs index c2b675ad1..7636dfa3d 100644 --- a/src/signalling/participants.rs +++ b/src/signalling/participants.rs @@ -179,7 +179,7 @@ impl ParticipantService { let member = self .get_member_by_id(member_id) .map_err(|_| RoomError::AuthorizationError)?; - if &member.credentials() == credentials { + if member.verify_credentials(credentials) { Ok(member) } else { Err(RoomError::AuthorizationError) @@ -461,7 +461,10 @@ impl ParticipantService { mod test { use std::time::Duration; - use crate::{api::control::pipeline::Pipeline, conf::Conf}; + use crate::{ + api::control::{member::Credential, pipeline::Pipeline}, + conf::Conf, + }; use super::*; @@ -486,7 +489,7 @@ mod test { let test_member_spec = MemberSpec::new( Pipeline::new(HashMap::new()), - "w/e".into(), + Credential::Plain("w/e".into()), None, None, None, @@ -528,7 +531,7 @@ mod test { let test_member_spec = MemberSpec::new( Pipeline::new(HashMap::new()), - "w/e".into(), + Credential::Plain("w/e".into()), None, None, Some(idle_timeout), diff --git a/src/signalling/peers/mod.rs b/src/signalling/peers/mod.rs index 171c15847..c655f97fc 100644 --- a/src/signalling/peers/mod.rs +++ b/src/signalling/peers/mod.rs @@ -762,6 +762,7 @@ mod tests { endpoints::webrtc_publish_endpoint::{ AudioSettings, P2pMode, VideoSettings, }, + member::Credential, refs::SrcUri, }, signalling::{ @@ -907,7 +908,7 @@ mod tests { let publisher = Member::new( "publisher".into(), - "test".into(), + Credential::Plain("test".into()), "test".into(), Duration::from_secs(10), Duration::from_secs(10), @@ -915,7 +916,7 @@ mod tests { ); let receiver = Member::new( "receiver".into(), - "test".into(), + Credential::Plain("test".into()), "test".into(), Duration::from_secs(10), Duration::from_secs(10), @@ -1012,7 +1013,7 @@ mod tests { let publisher = Member::new( "publisher".into(), - "test".into(), + Credential::Plain("test".into()), "test".into(), Duration::from_secs(10), Duration::from_secs(10), @@ -1020,7 +1021,7 @@ mod tests { ); let receiver = Member::new( "receiver".into(), - "test".into(), + Credential::Plain("test".into()), "test".into(), Duration::from_secs(10), Duration::from_secs(10), diff --git a/src/signalling/room/rpc_server.rs b/src/signalling/room/rpc_server.rs index c822e0d89..0c6367629 100644 --- a/src/signalling/room/rpc_server.rs +++ b/src/signalling/room/rpc_server.rs @@ -313,7 +313,9 @@ mod test { use super::*; use crate::{ - api::control::{pipeline::Pipeline, MemberSpec, RoomSpec}, + api::control::{ + member::Credential, pipeline::Pipeline, MemberSpec, RoomSpec, + }, conf::{self, Conf}, media::peer::tests::dummy_negotiation_sub_mock, signalling::{ @@ -355,7 +357,7 @@ mod test { let member1 = MemberSpec::new( Pipeline::new(HashMap::new()), - "w/e".into(), + Credential::Plain(String::from("w/e")), None, None, None, @@ -396,7 +398,7 @@ mod test { let member1 = MemberSpec::new( Pipeline::new(HashMap::new()), - "w/e".into(), + Credential::Plain(String::from("w/e")), None, None, None, @@ -436,7 +438,8 @@ mod test { use actix::Addr; use medea_client_api_proto::{ - CloseDescription, CloseReason, Credential, MemberId, RoomId, + self as client_proto, CloseDescription, CloseReason, MemberId, + RoomId, }; use mockall::predicate::eq; use serial_test::serial; @@ -450,6 +453,7 @@ mod test { }, url::CallbackUrl, }, + member::Credential, RoomElement, }, }; @@ -473,7 +477,7 @@ mod test { let id = MemberId::from("member"); let member = RoomElement::Member { spec: Pipeline::new(HashMap::new()), - credentials: Credential::from("test"), + credentials: Credential::Plain(String::from("test")), on_leave, on_join, idle_timeout: None, @@ -524,7 +528,7 @@ mod test { room.connection_established( MemberId::from("member"), - Credential::from("test"), + client_proto::Credential::from("test"), Box::new(MockRpcConnection::new()), ) .await @@ -556,14 +560,14 @@ mod test { .return_once(|_, _| Box::pin(future::ready(()))); room.connection_established( MemberId::from("member"), - Credential::from("test"), + client_proto::Credential::from("test"), Box::new(rpc_connection), ) .await .unwrap(); room.connection_established( MemberId::from("member"), - Credential::from("test"), + client_proto::Credential::from("test"), Box::new(MockRpcConnection::new()), ) .await @@ -581,7 +585,7 @@ mod test { room.connection_established( MemberId::from("member"), - Credential::from("test"), + client_proto::Credential::from("test"), Box::new(MockRpcConnection::new()), ) .await @@ -603,7 +607,7 @@ mod test { room.connection_established( MemberId::from("member"), - Credential::from("test"), + client_proto::Credential::from("test"), Box::new(MockRpcConnection::new()), ) .await @@ -622,7 +626,7 @@ mod test { room.connection_established( MemberId::from("member"), - Credential::from("test"), + client_proto::Credential::from("test"), Box::new(MockRpcConnection::new()), ) .await diff --git a/src/signalling/room_service.rs b/src/signalling/room_service.rs index 4d95d4f9a..ac71652af 100644 --- a/src/signalling/room_service.rs +++ b/src/signalling/room_service.rs @@ -10,7 +10,7 @@ use failure::Fail; use futures::future::{ self, FutureExt as _, LocalBoxFuture, TryFutureExt as _, }; -use medea_client_api_proto::{Credential, MemberId, RoomId}; +use medea_client_api_proto::{MemberId, RoomId}; use medea_control_api_proto::grpc::api as proto; use redis::RedisError; @@ -18,6 +18,7 @@ use crate::{ api::control::{ endpoints::EndpointSpec, load_static_specs_from_dir, + member::Credential, refs::{Fid, StatefulFid, ToMember, ToRoom}, EndpointId, LoadStaticControlSpecsError, MemberSpec, RoomSpec, TryFromElementError, @@ -187,16 +188,29 @@ impl RoomService { /// Returns [Control API] sid based on provided arguments and /// `MEDEA_SERVER__CLIENT__HTTP__PUBLIC_URL` config value. + /// + /// Returns sid with a token (`?token=`) if [`ControlCredential`] is + /// [`ControlCredential::Plain`]. + /// + /// Returns sid without token if [`ControlCredential`] is + /// [`ControlCredential::Hash`]. fn get_sid( &self, room_id: &RoomId, member_id: &MemberId, credentials: &Credential, ) -> String { - format!( - "{}/{}/{}/{}", - self.public_url, room_id, member_id, credentials - ) + match credentials { + Credential::Hash(_) => { + format!("{}/{}/{}", self.public_url, room_id, member_id) + } + Credential::Plain(plain) => { + format!( + "{}/{}/{}?token={}", + self.public_url, room_id, member_id, plain, + ) + } + } } } diff --git a/tests/e2e/callbacks/member.rs b/tests/e2e/callbacks/member.rs index 7199f5677..9bae346d1 100644 --- a/tests/e2e/callbacks/member.rs +++ b/tests/e2e/callbacks/member.rs @@ -61,6 +61,7 @@ async fn callback_test(name: &str, port: u16) -> CallbackTestItem { None, TestMember::DEFAULT_DEADLINE, true, + true, ) .await; (client, callback_server) diff --git a/tests/e2e/grpc_control_api/create.rs b/tests/e2e/grpc_control_api/create.rs index 44339380f..e05342c0f 100644 --- a/tests/e2e/grpc_control_api/create.rs +++ b/tests/e2e/grpc_control_api/create.rs @@ -7,7 +7,7 @@ use function_name::named; use medea::api::control::error_codes::ErrorCode; -use medea_control_api_proto::grpc::api as proto; +use medea_control_api_proto::grpc::api::{self as proto, member::Credentials}; use crate::{ enum_eq, @@ -35,7 +35,10 @@ mod room { sids.get(&"responder".to_string()).unwrap().as_str(); assert_eq!( responder_sid, - &format!("ws://127.0.0.1:8080/ws/{}/responder/test", test_name!()) + &format!( + "ws://127.0.0.1:8080/ws/{}/responder?token=test", + test_name!() + ) ); let mut room = take_room(client.get(test_name!()).await); @@ -45,7 +48,10 @@ mod room { proto::room::element::El::Member(member) => member, _ => panic!(), }; - assert_eq!(responder.credentials.as_str(), "test"); + assert_eq!( + responder.credentials, + Some(Credentials::Plain(String::from("test"))) + ); let mut responder_pipeline = responder.pipeline; assert_eq!(responder_pipeline.len(), 1); let responder_play = responder_pipeline.remove("play").unwrap(); @@ -63,8 +69,14 @@ mod room { proto::room::element::El::Member(member) => member, _ => panic!(), }; - assert_ne!(publisher.credentials.as_str(), "test"); - assert_ne!(publisher.credentials.as_str(), ""); + assert_ne!( + publisher.credentials, + Some(Credentials::Plain(String::from("test"))) + ); + assert_ne!( + publisher.credentials, + Some(Credentials::Plain(String::from(""))) + ); let publisher_pipeline = publisher.pipeline; assert_eq!(publisher_pipeline.len(), 1); } @@ -120,7 +132,7 @@ mod member { let add_member = MemberBuilder::default() .id("test-member") - .credentials("qwerty") + .credentials(Credentials::Plain(String::from("qwerty"))) .add_endpoint( WebRtcPlayEndpointBuilder::default() .id("play") @@ -138,7 +150,7 @@ mod member { assert_eq!( e2e_test_member_sid, format!( - "ws://127.0.0.1:8080/ws/{}/test-member/qwerty", + "ws://127.0.0.1:8080/ws/{}/test-member?token=qwerty", test_name!() ) ); @@ -146,7 +158,10 @@ mod member { let member = client.get(&format!("{}/test-member", test_name!())).await; let member = take_member(member); assert_eq!(member.pipeline.len(), 1); - assert_eq!(member.credentials.as_str(), "qwerty"); + assert_eq!( + member.credentials, + Some(Credentials::Plain(String::from("qwerty"))) + ); } #[actix_rt::test] @@ -406,6 +421,7 @@ mod endpoint { None, None, true, + true, ) .await; let _responder = TestMember::connect( @@ -433,6 +449,7 @@ mod endpoint { None, None, true, + true, ) .await; diff --git a/tests/e2e/grpc_control_api/credentials.rs b/tests/e2e/grpc_control_api/credentials.rs new file mode 100644 index 000000000..48b41dc52 --- /dev/null +++ b/tests/e2e/grpc_control_api/credentials.rs @@ -0,0 +1,167 @@ +use actix::Context; +use function_name::named; +use futures::{channel::mpsc, StreamExt}; +use medea_client_api_proto::{CloseReason, Event}; +use medea_control_api_proto::grpc::api::member::Credentials; + +use crate::{ + grpc_control_api::ControlClient, signalling::TestMember, test_name, +}; + +use super::{MemberBuilder, RoomBuilder}; + +/// [Argon2] hash of the word `medea`. +/// +/// [Argon2]: https://en.wikipedia.org/wiki/Argon2 +const MEDEA_CRED_HASH: &str = + "$argon2i$v=19$m=16,t=2,p=1$ZHNtcEFmVnREZkRtNk9hOA$6z1z/KA2FnBJA7fqqpdBQA"; + +/// Creates new `Room` with a provided `name` and `credentials`. +/// +/// ## Spec +/// +/// ```yaml +/// kind: Room +/// id: {{ room_id }} +/// spec: +/// pipeline: +/// member: +/// kind: Member +/// credentials: +/// plain: {{ credentials }} # Credentials::Plain +/// hash: {{ credentials }} # Credentials::Hash +/// spec: +/// pipeline: +/// play: +/// kind: WebRtcPlayEndpoint +/// spec: +/// src: "local://{{ room_id }}/publisher/publish" +/// ``` +async fn create_test_room(name: &str, credentials: Credentials) { + let mut control_client = ControlClient::new().await; + + let create_room = RoomBuilder::default() + .id(name) + .add_member( + MemberBuilder::default() + .id("member") + .credentials(credentials) + .build() + .unwrap(), + ) + .build() + .unwrap() + .build_request(""); + + control_client.create(create_room).await; +} + +/// Joins `Room` with a provided URL. +/// +/// Waits for first [`Event`] from server and returns it. +async fn join_room(url: &str) -> Option { + let (tx, mut rx) = mpsc::unbounded(); + TestMember::connect( + url, + Some(Box::new( + move |event: &Event, + _: &mut Context, + _: Vec<&Event>| { + tx.unbounded_send(event.clone()).unwrap(); + }, + )), + None, + TestMember::DEFAULT_DEADLINE, + false, + false, + ) + .await; + + rx.next().await +} + +/// Checks that Client will be rejected on invalid plain text credentials. +#[actix_rt::test] +#[named] +async fn invalid_plain_credentials() { + create_test_room(test_name!(), Credentials::Plain(String::from("test"))) + .await; + + assert_eq!( + join_room(&format!( + "ws://127.0.0.1:8080/ws/{}/member?token=test2", + test_name!(), + )) + .await + .unwrap(), + Event::RoomLeft { + close_reason: CloseReason::Rejected + } + ); +} + +/// Checks that Client will be rejected on invalid hash credentials. +#[actix_rt::test] +#[named] +async fn invalid_hash_credentials() { + create_test_room( + test_name!(), + Credentials::Hash(MEDEA_CRED_HASH.to_string()), + ) + .await; + + assert_eq!( + join_room(&format!( + "ws://127.0.0.1:8080/ws/{}/member?token=foobar", + test_name!(), + )) + .await + .unwrap(), + Event::RoomLeft { + close_reason: CloseReason::Rejected + } + ); +} + +/// Checks that Client will be accepted on valid plain text credentials. +#[actix_rt::test] +#[named] +async fn valid_hash_credentials() { + create_test_room( + test_name!(), + Credentials::Hash(MEDEA_CRED_HASH.to_string()), + ) + .await; + + assert_eq!( + join_room(&format!( + "ws://127.0.0.1:8080/ws/{}/member?token=medea", + test_name!(), + )) + .await + .unwrap(), + Event::RoomJoined { + member_id: "member".into(), + } + ); +} + +/// Checks that Client will be accepted on valid hash credentials. +#[actix_rt::test] +#[named] +async fn valid_plain_credentials() { + create_test_room(test_name!(), Credentials::Plain("medea".to_string())) + .await; + + assert_eq!( + join_room(&format!( + "ws://127.0.0.1:8080/ws/{}/member?token=medea", + test_name!(), + )) + .await + .unwrap(), + Event::RoomJoined { + member_id: "member".into(), + } + ); +} diff --git a/tests/e2e/grpc_control_api/mod.rs b/tests/e2e/grpc_control_api/mod.rs index 8c298a280..b31548aad 100644 --- a/tests/e2e/grpc_control_api/mod.rs +++ b/tests/e2e/grpc_control_api/mod.rs @@ -4,6 +4,7 @@ //! [Control API]: https://tinyurl.com/yxsqplq7 mod create; +mod credentials; mod delete; mod rpc_settings; mod signaling; @@ -12,7 +13,7 @@ use std::{collections::HashMap, time::Duration}; use derive_builder::*; use medea_control_api_proto::grpc::api::{ - self as proto, control_api_client::ControlApiClient, + self as proto, control_api_client::ControlApiClient, member::Credentials, }; use tonic::transport::Channel; @@ -201,7 +202,7 @@ pub struct Member { id: String, #[builder(default = "None")] #[builder(setter(strip_option))] - credentials: Option, + credentials: Option, #[builder(default = "HashMap::new()")] endpoints: HashMap, #[builder(default = "None")] @@ -231,7 +232,7 @@ impl Into for Member { pipeline, on_leave: self.on_leave.unwrap_or_default(), on_join: self.on_join.unwrap_or_default(), - credentials: self.credentials.unwrap_or_default(), + credentials: self.credentials, ping_interval: self.ping_interval.map(Into::into), idle_timeout: self.idle_timeout.map(Into::into), reconnect_timeout: self.reconnect_timeout.map(Into::into), @@ -394,7 +395,8 @@ impl Into for WebRtcPublishEndpoint { /// p2p: Always /// responder: /// kind: Member -/// credentials: test +/// credentials: +/// plain: test /// spec: /// pipeline: /// play: @@ -421,7 +423,7 @@ pub fn create_room_req(room_id: &str) -> proto::CreateRequest { .add_member( MemberBuilder::default() .id("responder") - .credentials("test") + .credentials(Credentials::Plain(String::from("test"))) .add_endpoint( WebRtcPlayEndpointBuilder::default() .id("play") @@ -461,7 +463,8 @@ pub fn create_room_req(room_id: &str) -> proto::CreateRequest { /// src: "local://{{ room_id }}/bob/publish" /// bob: /// kind: Member -/// credentials: test +/// credentials: +/// plain: test /// spec: /// pipeline: /// play: @@ -495,7 +498,7 @@ pub fn pub_pub_room_req(room_id: &str) -> proto::CreateRequest { .add_member( MemberBuilder::default() .id("bob") - .credentials("test") + .credentials(Credentials::Plain(String::from("test"))) .add_endpoint( WebRtcPublishEndpointBuilder::default() .id("publish") diff --git a/tests/e2e/grpc_control_api/rpc_settings.rs b/tests/e2e/grpc_control_api/rpc_settings.rs index 5d40999bc..60d6b4fc5 100644 --- a/tests/e2e/grpc_control_api/rpc_settings.rs +++ b/tests/e2e/grpc_control_api/rpc_settings.rs @@ -3,6 +3,7 @@ use std::time::{Duration, Instant}; use futures::channel::oneshot; +use medea_control_api_proto::grpc::api::member::Credentials; use crate::{ grpc_control_api::{ControlClient, MemberBuilder, RoomBuilder}, @@ -32,7 +33,7 @@ async fn rpc_settings_from_spec_works() { .add_member( MemberBuilder::default() .id("member") - .credentials("test") + .credentials(Credentials::Plain(String::from("test"))) .ping_interval(Some(Duration::from_secs(10))) .idle_timeout(Some(Duration::from_secs(1))) .reconnect_timeout(Some(Duration::from_secs(0))) @@ -49,7 +50,7 @@ async fn rpc_settings_from_spec_works() { let mut opened = None; TestMember::start( - format!("ws://127.0.0.1:8080/ws/{}/member/test", ROOM_ID), + format!("ws://127.0.0.1:8080/ws/{}/member?token=test", ROOM_ID), None, Some(Box::new(move |event| match event { ConnectionEvent::Started => { diff --git a/tests/e2e/grpc_control_api/signaling.rs b/tests/e2e/grpc_control_api/signaling.rs index e67b3093a..5fa9c9360 100644 --- a/tests/e2e/grpc_control_api/signaling.rs +++ b/tests/e2e/grpc_control_api/signaling.rs @@ -13,7 +13,9 @@ use actix::{Arbiter, Context}; use function_name::named; use futures::{channel::mpsc, StreamExt as _}; use medea_client_api_proto::Event; -use medea_control_api_proto::grpc::api::web_rtc_publish_endpoint::P2p; +use medea_control_api_proto::grpc::api::{ + member::Credentials, web_rtc_publish_endpoint::P2p, +}; use tokio::time::timeout; use crate::{ @@ -63,7 +65,7 @@ async fn signalling_starts_when_create_play_member_after_pub_member() { .add_member( MemberBuilder::default() .id("publisher") - .credentials("test") + .credentials(Credentials::Plain(String::from("test"))) .add_endpoint( WebRtcPublishEndpointBuilder::default() .id("publish") @@ -83,17 +85,21 @@ async fn signalling_starts_when_create_play_member_after_pub_member() { let (on_event, done) = done_on_both_peers_created(); TestMember::connect( - &format!("ws://127.0.0.1:8080/ws/{}/publisher/test", test_name!()), + &format!( + "ws://127.0.0.1:8080/ws/{}/publisher?token=test", + test_name!() + ), Some(Box::new(on_event.clone())), None, TestMember::DEFAULT_DEADLINE, true, + true, ) .await; let create_play_member = MemberBuilder::default() .id("responder") - .credentials("qwerty") + .credentials(Credentials::Plain(String::from("qwerty"))) .add_endpoint( WebRtcPlayEndpointBuilder::default() .id("play") @@ -107,11 +113,15 @@ async fn signalling_starts_when_create_play_member_after_pub_member() { control_client.create(create_play_member).await; TestMember::connect( - &format!("ws://127.0.0.1:8080/ws/{}/responder/qwerty", test_name!()), + &format!( + "ws://127.0.0.1:8080/ws/{}/responder?token=qwerty", + test_name!() + ), Some(Box::new(on_event)), None, TestMember::DEFAULT_DEADLINE, true, + true, ) .await; @@ -128,7 +138,7 @@ async fn signalling_starts_when_create_play_endpoint_after_pub_member() { .add_member( MemberBuilder::default() .id("publisher") - .credentials("test") + .credentials(Credentials::Plain(String::from("test"))) .add_endpoint( WebRtcPublishEndpointBuilder::default() .id("publish") @@ -148,17 +158,21 @@ async fn signalling_starts_when_create_play_endpoint_after_pub_member() { let (on_event, done) = done_on_both_peers_created(); TestMember::connect( - &format!("ws://127.0.0.1:8080/ws/{}/publisher/test", test_name!()), + &format!( + "ws://127.0.0.1:8080/ws/{}/publisher?token=test", + test_name!() + ), Some(Box::new(on_event.clone())), None, TestMember::DEFAULT_DEADLINE, true, + true, ) .await; let create_second_member = MemberBuilder::default() .id("responder") - .credentials("qwerty") + .credentials(Credentials::Plain(String::from("qwerty"))) .build() .unwrap() .build_request(test_name!()); @@ -174,11 +188,15 @@ async fn signalling_starts_when_create_play_endpoint_after_pub_member() { control_client.create(create_play).await; TestMember::connect( - &format!("ws://127.0.0.1:8080/ws/{}/responder/qwerty", test_name!()), + &format!( + "ws://127.0.0.1:8080/ws/{}/responder?token=qwerty", + test_name!() + ), Some(Box::new(on_event)), None, TestMember::DEFAULT_DEADLINE, true, + true, ) .await; @@ -195,7 +213,7 @@ async fn signalling_starts_in_loopback_scenario() { .add_member( MemberBuilder::default() .id("publisher") - .credentials("test") + .credentials(Credentials::Plain(String::from("test"))) .add_endpoint( WebRtcPublishEndpointBuilder::default() .id("publish") @@ -215,11 +233,15 @@ async fn signalling_starts_in_loopback_scenario() { let (on_event, done) = done_on_both_peers_created(); TestMember::connect( - &format!("ws://127.0.0.1:8080/ws/{}/publisher/test", test_name!()), + &format!( + "ws://127.0.0.1:8080/ws/{}/publisher?token=test", + test_name!() + ), Some(Box::new(on_event)), None, TestMember::DEFAULT_DEADLINE, true, + true, ) .await; @@ -245,7 +267,7 @@ async fn peers_removed_on_delete_member() { .add_member( MemberBuilder::default() .id("publisher") - .credentials("test") + .credentials(Credentials::Plain(String::from("test"))) .add_endpoint( WebRtcPublishEndpointBuilder::default() .id("publish") @@ -259,7 +281,7 @@ async fn peers_removed_on_delete_member() { .add_member( MemberBuilder::default() .id("responder") - .credentials("test") + .credentials(Credentials::Plain(String::from("test"))) .add_endpoint( WebRtcPlayEndpointBuilder::default() .id("play") @@ -306,13 +328,19 @@ async fn peers_removed_on_delete_member() { let deadline = Some(Duration::from_secs(5)); TestMember::start( - format!("ws://127.0.0.1:8080/ws/{}/publisher/test", test_name!()), + format!( + "ws://127.0.0.1:8080/ws/{}/publisher?token=test", + test_name!() + ), Some(Box::new(on_event.clone())), None, deadline, ); TestMember::start( - format!("ws://127.0.0.1:8080/ws/{}/responder/test", test_name!()), + format!( + "ws://127.0.0.1:8080/ws/{}/responder?token=test", + test_name!() + ), Some(Box::new(on_event)), None, deadline, diff --git a/tests/e2e/signalling/add_endpoints_synchronization.rs b/tests/e2e/signalling/add_endpoints_synchronization.rs index 5f9caa78a..d057ea410 100644 --- a/tests/e2e/signalling/add_endpoints_synchronization.rs +++ b/tests/e2e/signalling/add_endpoints_synchronization.rs @@ -3,7 +3,7 @@ use std::time::Duration; use function_name::named; use futures::{channel::mpsc, StreamExt}; use medea_client_api_proto::{Direction, Event, TrackUpdate}; -use medea_control_api_proto::grpc::api::{self as proto}; +use medea_control_api_proto::grpc::api::{self as proto, member::Credentials}; use tokio::time::delay_for; use crate::{ @@ -35,7 +35,7 @@ pub fn create_room_req(room_id: &str) -> proto::CreateRequest { .add_member( MemberBuilder::default() .id("second") - .credentials("test") + .credentials(Credentials::Plain(String::from("test"))) .add_endpoint( WebRtcPublishEndpointBuilder::default() .id("publish") @@ -74,6 +74,7 @@ async fn add_endpoints_synchronization() { None, TestMember::DEFAULT_DEADLINE, true, + true, ) .await; @@ -86,6 +87,7 @@ async fn add_endpoints_synchronization() { None, TestMember::DEFAULT_DEADLINE, false, + true, ) .await; diff --git a/tests/e2e/signalling/command_validation.rs b/tests/e2e/signalling/command_validation.rs index a8504cd7b..d9a6ee711 100644 --- a/tests/e2e/signalling/command_validation.rs +++ b/tests/e2e/signalling/command_validation.rs @@ -4,7 +4,9 @@ use actix::Context; use function_name::named; use futures::{channel::mpsc::unbounded, StreamExt as _}; use medea_client_api_proto::{Command, Event, IceCandidate, PeerId}; -use medea_control_api_proto::grpc::api::web_rtc_publish_endpoint::P2p; +use medea_control_api_proto::grpc::api::{ + member::Credentials, web_rtc_publish_endpoint::P2p, +}; use crate::{ grpc_control_api::{ @@ -27,7 +29,7 @@ async fn command_validation() { .add_member( MemberBuilder::default() .id("publisher") - .credentials("test") + .credentials(Credentials::Plain(String::from("test"))) .add_endpoint( WebRtcPublishEndpointBuilder::default() .id("publish") @@ -41,7 +43,7 @@ async fn command_validation() { .add_member( MemberBuilder::default() .id("responder") - .credentials("test") + .credentials(Credentials::Plain(String::from("test"))) .add_endpoint( WebRtcPlayEndpointBuilder::default() .id("play") @@ -63,7 +65,10 @@ async fn command_validation() { let (tx1, mut rx1) = unbounded(); let member1 = TestMember::connect( - &format!("ws://127.0.0.1:8080/ws/{}/publisher/test", test_name!()), + &format!( + "ws://127.0.0.1:8080/ws/{}/publisher?token=test", + test_name!() + ), Some(Box::new( move |event: &Event, _: &mut Context, @@ -74,12 +79,16 @@ async fn command_validation() { None, TestMember::DEFAULT_DEADLINE, true, + true, ) .await; let (tx2, mut rx2) = unbounded(); TestMember::start( - format!("ws://127.0.0.1:8080/ws/{}/responder/test", test_name!()), + format!( + "ws://127.0.0.1:8080/ws/{}/responder?token=test", + test_name!() + ), Some(Box::new( move |event: &Event, _: &mut Context, diff --git a/tests/e2e/signalling/ice_restart.rs b/tests/e2e/signalling/ice_restart.rs index 979c84237..5cd04d954 100644 --- a/tests/e2e/signalling/ice_restart.rs +++ b/tests/e2e/signalling/ice_restart.rs @@ -41,6 +41,7 @@ async fn ice_restart() { None, TestMember::DEFAULT_DEADLINE, false, + true, ) .await; @@ -57,6 +58,7 @@ async fn ice_restart() { None, TestMember::DEFAULT_DEADLINE, false, + true, ) .await; diff --git a/tests/e2e/signalling/mod.rs b/tests/e2e/signalling/mod.rs index 2fe105c03..56c468bed 100644 --- a/tests/e2e/signalling/mod.rs +++ b/tests/e2e/signalling/mod.rs @@ -88,15 +88,25 @@ pub struct TestMember { /// Whether to handle negotiation in [`TestMember`]. auto_negotiation: bool, + + /// Whether to handle room management ([`Event::RoomJoined`] / + /// [`Event::RoomLeft`]) in [`TestMember`]. + auto_room_management: bool, } pub fn parse_join_room_url(url: &str) -> (Url, RoomId, MemberId, Credential) { let mut url = Url::parse(&url).unwrap(); url.set_fragment(None); + let token = url + .query_pairs() + .find(|(key, _)| key.as_ref() == "token") + .unwrap() + .1 + .into_owned() + .into(); url.set_query(None); let mut segments = url.path_segments().unwrap().rev(); - let token = segments.next().unwrap().to_owned().into(); let member_id = segments.next().unwrap().to_owned().into(); let room_id = segments.next().unwrap().to_owned().into(); url.set_path("/ws"); @@ -147,6 +157,7 @@ impl TestMember { on_connection_event: Option, deadline: Option, auto_negotiation: bool, + auto_room_management: bool, ) -> Addr { let (url, room_id, member_id, token) = parse_join_room_url(url); let (_, framed) = @@ -167,6 +178,7 @@ impl TestMember { on_message, on_connection_event, auto_negotiation, + auto_room_management, }; this.authorize(member_id, token); @@ -194,6 +206,7 @@ impl TestMember { on_connection_event, deadline, true, + true, ) .await; }) @@ -298,11 +311,13 @@ impl StreamHandler> for TestMember { ServerMsg::Ping(id) => self.send_pong(id), ServerMsg::Event { room_id, event } => { assert_eq!(self.room_id, room_id); - if matches!( - event, - Event::RoomJoined { .. } | Event::RoomLeft { .. } - ) { - return; + if self.auto_room_management { + if matches!( + event, + Event::RoomJoined { .. } | Event::RoomLeft { .. } + ) { + return; + } } if self.auto_negotiation { match &event { diff --git a/tests/e2e/signalling/pub_sub_signallng.rs b/tests/e2e/signalling/pub_sub_signallng.rs index 66b74375a..72fa03a6e 100644 --- a/tests/e2e/signalling/pub_sub_signallng.rs +++ b/tests/e2e/signalling/pub_sub_signallng.rs @@ -98,13 +98,13 @@ fn pub_sub_video_call() { let deadline = Some(std::time::Duration::from_secs(5)); TestMember::start( - format!("{}/responder/test", base_url), + format!("{}/responder?token=test", base_url), Some(Box::new(test_fn)), None, deadline, ); TestMember::start( - format!("{}/caller/test", base_url), + format!("{}/caller?token=test", base_url), Some(Box::new(test_fn)), None, deadline, diff --git a/tests/e2e/signalling/rpc_settings.rs b/tests/e2e/signalling/rpc_settings.rs index c9906817b..d00226f7d 100644 --- a/tests/e2e/signalling/rpc_settings.rs +++ b/tests/e2e/signalling/rpc_settings.rs @@ -3,6 +3,7 @@ use std::time::Duration; use futures::channel::oneshot; +use medea_control_api_proto::grpc::api::member::Credentials; use crate::{ grpc_control_api::{ControlClient, MemberBuilder, RoomBuilder}, @@ -23,7 +24,7 @@ async fn rpc_settings_server_msg() { .add_member( MemberBuilder::default() .id("member") - .credentials("test") + .credentials(Credentials::Plain(String::from("test"))) .ping_interval(Some(Duration::from_secs( PING_INTERVAL_SECS.into(), ))) @@ -43,7 +44,7 @@ async fn rpc_settings_server_msg() { let mut end_tx = Some(end_tx); let mut is_initial_settings_received = false; TestMember::start( - format!("ws://127.0.0.1:8080/ws/{}/member/test", ROOM_ID), + format!("ws://127.0.0.1:8080/ws/{}/member?token=test", ROOM_ID), None, Some(Box::new(move |event| { if let ConnectionEvent::SettingsReceived(settings) = event { diff --git a/tests/e2e/signalling/three_pubs.rs b/tests/e2e/signalling/three_pubs.rs index 1d24a7159..894d6d148 100644 --- a/tests/e2e/signalling/three_pubs.rs +++ b/tests/e2e/signalling/three_pubs.rs @@ -114,19 +114,19 @@ fn three_members_p2p_video_call() { let deadline = Some(std::time::Duration::from_secs(5)); TestMember::start( - format!("{}/member-1/test", base_url), + format!("{}/member-1?token=test", base_url), Some(Box::new(test_fn.clone())), None, deadline, ); TestMember::start( - format!("{}/member-2/test", base_url), + format!("{}/member-2?token=test", base_url), Some(Box::new(test_fn.clone())), None, deadline, ); TestMember::start( - format!("{}/member-3/test", base_url), + format!("{}/member-3?token=test", base_url), Some(Box::new(test_fn)), None, deadline, diff --git a/tests/e2e/signalling/track_disable.rs b/tests/e2e/signalling/track_disable.rs index 37851044f..01e1b8fa5 100644 --- a/tests/e2e/signalling/track_disable.rs +++ b/tests/e2e/signalling/track_disable.rs @@ -121,6 +121,7 @@ async fn track_disables_and_enables() { None, TestMember::DEFAULT_DEADLINE, true, + true, ) .await; let (subscriber_tx, mut subscriber_rx) = mpsc::unbounded(); @@ -132,6 +133,7 @@ async fn track_disables_and_enables() { None, TestMember::DEFAULT_DEADLINE, true, + true, ) .await; @@ -200,6 +202,7 @@ async fn track_disables_and_enables_are_instant() { None, TestMember::DEFAULT_DEADLINE, true, + true, ) .await; @@ -212,6 +215,7 @@ async fn track_disables_and_enables_are_instant() { None, TestMember::DEFAULT_DEADLINE, true, + true, ) .await; @@ -311,6 +315,7 @@ async fn track_disables_and_enables_are_instant2() { None, TestMember::DEFAULT_DEADLINE, false, + true, ) .await; @@ -323,6 +328,7 @@ async fn track_disables_and_enables_are_instant2() { None, TestMember::DEFAULT_DEADLINE, false, + true, ) .await; @@ -469,6 +475,7 @@ async fn force_update_works() { })), TestMember::DEFAULT_DEADLINE, true, + true, ) .await; @@ -519,6 +526,7 @@ async fn force_update_works() { None, TestMember::DEFAULT_DEADLINE, true, + true, ) .await; @@ -562,6 +570,7 @@ async fn ordering_on_force_update_is_correct() { None, None, false, + true, ) .await; let (bob_events_tx, mut bob_events_rx) = mpsc::unbounded(); @@ -573,6 +582,7 @@ async fn ordering_on_force_update_is_correct() { None, None, false, + true, ) .await; @@ -882,6 +892,7 @@ async fn individual_and_general_mute_states_works() { None, TestMember::DEFAULT_DEADLINE, true, + true, ) .await; let _publisher = TestMember::connect( @@ -995,6 +1006,7 @@ async fn individual_and_general_mute_states_works() { None, TestMember::DEFAULT_DEADLINE, true, + true, ) .await; diff --git a/tests/e2e/signalling/track_mute.rs b/tests/e2e/signalling/track_mute.rs index bbdf1361a..0fe99829e 100644 --- a/tests/e2e/signalling/track_mute.rs +++ b/tests/e2e/signalling/track_mute.rs @@ -29,6 +29,7 @@ async fn track_mute_doesnt_renegotiates() { None, TestMember::DEFAULT_DEADLINE, true, + true, ) .await; let (subscriber_tx, mut subscriber_rx) = mpsc::unbounded(); @@ -40,6 +41,7 @@ async fn track_mute_doesnt_renegotiates() { None, TestMember::DEFAULT_DEADLINE, true, + true, ) .await; @@ -128,6 +130,7 @@ async fn track_mute_with_disable_will_start_renegotiation() { None, TestMember::DEFAULT_DEADLINE, true, + true, ) .await; let (subscriber_tx, mut subscriber_rx) = mpsc::unbounded(); @@ -139,6 +142,7 @@ async fn track_mute_with_disable_will_start_renegotiation() { None, TestMember::DEFAULT_DEADLINE, true, + true, ) .await; diff --git a/tests/specs/pub-sub-video-call.yml b/tests/specs/pub-sub-video-call.yml index a4b01f869..eced63a12 100644 --- a/tests/specs/pub-sub-video-call.yml +++ b/tests/specs/pub-sub-video-call.yml @@ -4,7 +4,8 @@ spec: pipeline: caller: kind: Member - credentials: test + credentials: + plain: test spec: pipeline: publish: @@ -14,7 +15,8 @@ spec: force_relay: true responder: kind: Member - credentials: test + credentials: + plain: test spec: pipeline: play: diff --git a/tests/specs/three-members-conference.yml b/tests/specs/three-members-conference.yml index 0583c08a4..cfaa2e6d7 100644 --- a/tests/specs/three-members-conference.yml +++ b/tests/specs/three-members-conference.yml @@ -4,7 +4,8 @@ spec: pipeline: member-1: kind: Member - credentials: test + credentials: + plain: test spec: pipeline: publish: @@ -21,7 +22,8 @@ spec: src: "local://three-members-conference/member-3/publish" member-2: kind: Member - credentials: test + credentials: + plain: test spec: pipeline: publish: @@ -38,7 +40,8 @@ spec: src: "local://three-members-conference/member-3/publish" member-3: kind: Member - credentials: test + credentials: + plain: test spec: pipeline: publish: