moved mcaptcha into embedded

This commit is contained in:
Aravinth Manivannan 2021-06-03 19:40:45 +05:30
parent e7dd8bff63
commit cc88c69fc6
Signed by: realaravinth
GPG Key ID: AD9F0F08E855ED88
8 changed files with 168 additions and 122 deletions

View File

@ -1,5 +1,9 @@
## 0.1.4
## Added:
- `Master` trait: provides methods to manage mcaptcha
## Changed:
- `PoWConfig` has an extra field to send internal `PoW` salt to clients.
@ -11,9 +15,14 @@
- `master::Master` is moved to `master::embedded::master` in preparation
for Redis based implementation.
- `crate::mcaptcha` is moved to `master::embedded::mcaptcha` in preparation
- `crate::mcaptcha` is moved to `master::embedded::counter` in preparation
for Redis based implementation.
- `AddSite` message for `Master` now requires an instance of
`crate::master::MCaptcha`. In the case of
`crate::master::embedded::master`, it automatically starts `Counter`
actor.
## 0.1.3
## Added

View File

@ -1,6 +1,7 @@
use libmcaptcha::{
cache::{messages::VerifyCaptchaResult, HashCache},
master::embedded::master::{AddSiteBuilder, Master},
master::embedded::master::Master,
master::AddSiteBuilder,
pow::{ConfigBuilder, Work},
system::SystemBuilder,
DefenseBuilder, LevelBuilder, MCaptchaBuilder,
@ -75,8 +76,7 @@ async fn main() -> std::io::Result<()> {
.duration(30)
// .cache(cache)
.build()
.unwrap()
.start();
.unwrap();
// unique value identifying an MCaptcha actor
let mcaptcha_name = "batsense.net";
@ -84,7 +84,7 @@ async fn main() -> std::io::Result<()> {
// add MCaptcha to Master
let msg = AddSiteBuilder::default()
.id(mcaptcha_name.into())
.addr(mcaptcha.clone())
.mcaptcha(mcaptcha)
.build()
.unwrap();
system.master.send(msg).await.unwrap();

View File

@ -48,7 +48,8 @@
//! ```rust
//! use libmcaptcha::{
//! cache::{messages::VerifyCaptchaResult, HashCache},
//! master::embedded::master::{AddSiteBuilder, Master},
//! master::embedded::master:: Master,
//! master::AddSiteBuilder,
//! pow::{ConfigBuilder, Work},
//! system::SystemBuilder,
//! DefenseBuilder, LevelBuilder, MCaptchaBuilder,
@ -123,8 +124,7 @@
//! .duration(30)
//! // .cache(cache)
//! .build()
//! .unwrap()
//! .start();
//! .unwrap();
//!
//! // unique value identifying an MCaptcha actor
//! let mcaptcha_name = "batsense.net";
@ -132,7 +132,7 @@
//! // add MCaptcha to Master
//! let msg = AddSiteBuilder::default()
//! .id(mcaptcha_name.into())
//! .addr(mcaptcha.clone())
//! .mcaptcha(mcaptcha)
//! .build()
//! .unwrap();
//! system.master.send(msg).await.unwrap();
@ -196,4 +196,5 @@ mod utils;
pub use crate::cache::hashcache::HashCache;
pub use defense::{Defense, DefenseBuilder, LevelBuilder};
pub use master::embedded::mcaptcha::{MCaptcha, MCaptchaBuilder};
pub use master::embedded::counter::Counter;
pub use master::MCaptchaBuilder;

View File

@ -15,11 +15,17 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//! MCaptcha actor module that manages defense levels
//! Counter actor module that manages defense levels
//!
//! ## Usage:
//! ```rust
//! use libmcaptcha::{master::embedded::mcaptcha::AddVisitor, MCaptchaBuilder, cache::HashCache, LevelBuilder, DefenseBuilder};
//! use libmcaptcha::{
//! master::embedded::counter::{Counter, AddVisitor},
//! master::MCaptchaBuilder,
//! cache::HashCache,
//! LevelBuilder,
//! DefenseBuilder
//! };
//! // traits from actix needs to be in scope for starting actor
//! use actix::prelude::*;
//!
@ -55,18 +61,20 @@
//! .build()
//! .unwrap();
//!
//! // create and start MCaptcha actor
//! // create and start Counter actor
//! //let cache = HashCache::default().start();
//! let mcaptcha = MCaptchaBuilder::default()
//! .defense(defense)
//! // leaky bucket algorithm's emission interval
//! .duration(30)
//! .build()
//! .unwrap()
//! .start();
//! .unwrap();
//!
//! let counter: Counter = mcaptcha.into();
//! let counter = counter.start();
//!
//! // increment count when user visits protected routes
//! mcaptcha.send(AddVisitor).await.unwrap();
//! counter.send(AddVisitor).await.unwrap();
//!
//! Ok(())
//! }
@ -78,72 +86,29 @@ use std::time::Duration;
use actix::dev::*;
use serde::{Deserialize, Serialize};
use crate::{
defense::Defense,
errors::{CaptchaError, CaptchaResult},
master::AddVisitorResult,
};
/// Builder for [MCaptcha]
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct MCaptchaBuilder {
visitor_threshold: u32,
defense: Option<Defense>,
duration: Option<u64>,
}
impl Default for MCaptchaBuilder {
fn default() -> Self {
MCaptchaBuilder {
visitor_threshold: 0,
defense: None,
duration: None,
}
}
}
use crate::master::MCaptcha;
use crate::{defense::Defense, master::AddVisitorResult};
/// This struct represents the mCaptcha state and is used
/// to configure leaky-bucket lifetime and manage defense
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct MCaptcha {
pub struct Counter {
visitor_threshold: u32,
defense: Defense,
duration: u64,
}
impl MCaptchaBuilder {
/// set defense
pub fn defense(&mut self, d: Defense) -> &mut Self {
self.defense = Some(d);
self
}
/// set duration
pub fn duration(&mut self, d: u64) -> &mut Self {
self.duration = Some(d);
self
}
/// Builds new [MCaptcha]
pub fn build(&mut self) -> CaptchaResult<MCaptcha> {
if self.duration.is_none() {
Err(CaptchaError::PleaseSetValue("duration".into()))
} else if self.defense.is_none() {
Err(CaptchaError::PleaseSetValue("defense".into()))
} else if self.duration <= Some(0) {
Err(CaptchaError::CaptchaDurationZero)
} else {
let m = MCaptcha {
duration: self.duration.unwrap(),
defense: self.defense.clone().unwrap(),
visitor_threshold: self.visitor_threshold,
};
Ok(m)
}
impl From<MCaptcha> for Counter {
fn from(m: MCaptcha) -> Counter {
let m = Counter {
duration: m.duration,
defense: m.defense,
visitor_threshold: m.visitor_threshold,
};
m
}
}
impl MCaptcha {
impl Counter {
/// increments the visitor count by one
pub fn add_visitor(&mut self) {
self.visitor_threshold += 1;
@ -166,12 +131,12 @@ impl MCaptcha {
self.defense.get_difficulty()
}
/// get [MCaptcha]'s lifetime
/// get [Counter]'s lifetime
pub fn get_duration(&self) -> u64 {
self.duration
}
}
impl Actor for MCaptcha {
impl Actor for Counter {
type Context = Context<Self>;
}
@ -180,7 +145,7 @@ impl Actor for MCaptcha {
#[rtype(result = "()")]
struct DeleteVisitor;
impl Handler<DeleteVisitor> for MCaptcha {
impl Handler<DeleteVisitor> for Counter {
type Result = ();
fn handle(&mut self, _msg: DeleteVisitor, _ctx: &mut Self::Context) -> Self::Result {
self.decrement_visitor();
@ -194,7 +159,7 @@ impl Handler<DeleteVisitor> for MCaptcha {
pub struct AddVisitor;
impl AddVisitorResult {
fn new(m: &MCaptcha) -> Self {
fn new(m: &Counter) -> Self {
AddVisitorResult {
duration: m.get_duration(),
difficulty_factor: m.get_difficulty(),
@ -202,7 +167,7 @@ impl AddVisitorResult {
}
}
impl Handler<AddVisitor> for MCaptcha {
impl Handler<AddVisitor> for Counter {
type Result = MessageResult<AddVisitor>;
fn handle(&mut self, _: AddVisitor, ctx: &mut Self::Context) -> Self::Result {
@ -228,7 +193,7 @@ impl Handler<AddVisitor> for MCaptcha {
#[rtype(result = "u32")]
pub struct GetCurrentVisitorCount;
impl Handler<GetCurrentVisitorCount> for MCaptcha {
impl Handler<GetCurrentVisitorCount> for Counter {
type Result = MessageResult<GetCurrentVisitorCount>;
fn handle(&mut self, _: GetCurrentVisitorCount, _ctx: &mut Self::Context) -> Self::Result {
@ -236,12 +201,12 @@ impl Handler<GetCurrentVisitorCount> for MCaptcha {
}
}
/// Message to stop [MCaptcha]
/// Message to stop [Counter]
#[derive(Message)]
#[rtype(result = "()")]
pub struct Stop;
impl Handler<Stop> for MCaptcha {
impl Handler<Stop> for Counter {
type Result = ();
fn handle(&mut self, _: Stop, ctx: &mut Self::Context) -> Self::Result {
@ -253,6 +218,8 @@ impl Handler<Stop> for MCaptcha {
pub mod tests {
use super::*;
use crate::defense::*;
use crate::errors::*;
use crate::master::MCaptchaBuilder;
// constants for testing
// (visitor count, level)
@ -260,7 +227,7 @@ pub mod tests {
pub const LEVEL_2: (u32, u32) = (500, 500);
pub const DURATION: u64 = 5;
type MyActor = Addr<MCaptcha>;
type MyActor = Addr<Counter>;
pub fn get_defense() -> Defense {
DefenseBuilder::default()
@ -286,13 +253,17 @@ pub mod tests {
.unwrap()
}
async fn race(addr: Addr<MCaptcha>, count: (u32, u32)) {
async fn race(addr: Addr<Counter>, count: (u32, u32)) {
for _ in 0..count.0 as usize - 1 {
let _ = addr.send(AddVisitor).await.unwrap();
}
}
pub fn get_counter() -> MCaptcha {
pub fn get_counter() -> Counter {
get_mcaptcha().into()
}
pub fn get_mcaptcha() -> MCaptcha {
MCaptchaBuilder::default()
.defense(get_defense())
.duration(DURATION)

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//! [Master] actor module that manages [MCaptcha] actors
//! [Master] actor module that manages [Counter] actors
use std::collections::BTreeMap;
use std::sync::mpsc::channel;
use std::time::Duration;
@ -23,30 +23,28 @@ use std::time::Duration;
//use actix::clock::sleep;
use actix::clock::delay_for;
use actix::dev::*;
use derive_builder::Builder;
use log::info;
use super::mcaptcha::MCaptcha;
use crate::master::AddVisitor;
use super::counter::Counter;
use crate::master::Master as MasterTrait;
use crate::master::{AddSite, AddVisitor};
/// This Actor manages the [MCaptcha] actors.
/// A service can have several [MCaptcha] actors with
/// This Actor manages the [Counter] actors.
/// A service can have several [Counter] actors with
/// varying [Defense][crate::defense::Defense] configurations
/// so a "master" actor is needed to manage them all
#[derive(Clone, Default)]
pub struct Master {
sites: BTreeMap<String, (Option<()>, Addr<MCaptcha>)>,
sites: BTreeMap<String, (Option<()>, Addr<Counter>)>,
gc: u64,
}
impl MasterTrait for Master {}
impl Master {
/// add [MCaptcha] actor to [Master]
pub fn add_site(&mut self, details: AddSite) {
self.sites
.insert(details.id, (None, details.addr.to_owned()));
/// add [Counter] actor to [Master]
pub fn add_site(&mut self, addr: Addr<Counter>, id: String) {
self.sites.insert(id, (None, addr.to_owned()));
}
/// create new master
@ -58,8 +56,8 @@ impl Master {
}
}
/// get [MCaptcha] actor from [Master]
pub fn get_site<'a, 'b>(&'a mut self, id: &'b str) -> Option<Addr<MCaptcha>> {
/// get [Counter] actor from [Master]
pub fn get_site<'a, 'b>(&'a mut self, id: &'b str) -> Option<Addr<Counter>> {
let mut r = None;
if let Some((read_val, addr)) = self.sites.get_mut(id) {
r = Some(addr.clone());
@ -68,7 +66,7 @@ impl Master {
r
}
/// remvoes [MCaptcha] actor from [Master]
/// remvoes [Counter] actor from [Master]
pub fn rm_site(&mut self, id: &str) {
self.sites.remove(id);
}
@ -99,7 +97,7 @@ impl Handler<AddVisitor> for Master {
let fut = async move {
let config = addr
.unwrap()
.send(super::mcaptcha::AddVisitor)
.send(super::counter::AddVisitor)
.await
.unwrap();
@ -112,9 +110,9 @@ impl Handler<AddVisitor> for Master {
}
}
/// Message to get an [MCaptcha] actor from master
/// Message to get an [Counter] actor from master
#[derive(Message)]
#[rtype(result = "Option<Addr<MCaptcha>>")]
#[rtype(result = "Option<Addr<Counter>>")]
pub struct GetSite(pub String);
impl Handler<GetSite> for Master {
@ -130,7 +128,7 @@ impl Handler<GetSite> for Master {
}
}
/// Message to clean up master of [MCaptcha] actors with zero visitor count
/// Message to clean up master of [Counter] actors with zero visitor count
#[derive(Message)]
#[rtype(result = "()")]
pub struct CleanUp;
@ -145,7 +143,7 @@ impl Handler<CleanUp> for Master {
info!("init master actor cleanup up");
let task = async move {
for (id, (new, addr)) in sites.iter() {
use super::mcaptcha::{GetCurrentVisitorCount, Stop};
use super::counter::{GetCurrentVisitorCount, Stop};
let visitor_count = addr.send(GetCurrentVisitorCount).await.unwrap();
println!("{}", visitor_count);
if visitor_count == 0 && new.is_some() {
@ -165,7 +163,7 @@ impl Handler<CleanUp> for Master {
}
}
/// Message to delete [MCaptcha] actor
/// Message to delete [Counter] actor
#[derive(Message)]
#[rtype(result = "()")]
pub struct RemoveSite(pub String);
@ -178,43 +176,38 @@ impl Handler<RemoveSite> for Master {
}
}
/// Message to add an [MCaptcha] actor to [Master]
#[derive(Message, Builder)]
#[rtype(result = "()")]
pub struct AddSite {
pub id: String,
pub addr: Addr<MCaptcha>,
}
impl Handler<AddSite> for Master {
type Result = ();
fn handle(&mut self, m: AddSite, _ctx: &mut Self::Context) -> Self::Result {
self.add_site(m);
let counter: Counter = m.mcaptcha.into();
let addr = counter.start();
self.add_site(addr, m.id);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::master::embedded::mcaptcha::tests::*;
use crate::master::embedded::counter::tests::*;
use crate::master::AddSiteBuilder;
#[actix_rt::test]
async fn master_actor_works() {
let addr = Master::new(1).start();
let id = "yo";
let mcaptcha = get_counter().start();
let mcaptcha = get_mcaptcha();
let msg = AddSiteBuilder::default()
.id(id.into())
.addr(mcaptcha.clone())
.mcaptcha(mcaptcha.clone())
.build()
.unwrap();
addr.send(msg).await.unwrap();
let mcaptcha_addr = addr.send(GetSite(id.into())).await.unwrap();
assert_eq!(mcaptcha_addr, Some(mcaptcha));
assert!(mcaptcha_addr.is_some());
let addr_doesnt_exist = addr.send(GetSite("a".into())).await.unwrap();
assert!(addr_doesnt_exist.is_none());

View File

@ -16,5 +16,5 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
pub mod counter;
pub mod master;
pub mod mcaptcha;

View File

@ -19,12 +19,18 @@
use std::sync::mpsc::Receiver;
use actix::dev::*;
use derive_builder::Builder;
use serde::{Deserialize, Serialize};
pub mod embedded;
use crate::defense::Defense;
use crate::errors::*;
/// Describes actor handler trait impls that are required by a cache implementation
pub trait Master: actix::Actor + actix::Handler<AddVisitor> {}
pub trait Master: actix::Actor + actix::Handler<AddVisitor> + actix::Handler<AddSite> {}
//+ actix::Handler<AddSite>
/// Message to add visitor to an [MCaptcha] actor
#[derive(Message)]
@ -39,3 +45,68 @@ pub struct AddVisitorResult {
pub duration: u64,
pub difficulty_factor: u32,
}
/// Message to add an [Counter] actor to [Master]
#[derive(Message, Builder)]
#[rtype(result = "()")]
pub struct AddSite {
pub id: String,
pub mcaptcha: MCaptcha,
}
/// Builder for [MCaptcha]
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct MCaptchaBuilder {
visitor_threshold: u32,
defense: Option<Defense>,
duration: Option<u64>,
}
impl Default for MCaptchaBuilder {
fn default() -> Self {
MCaptchaBuilder {
visitor_threshold: 0,
defense: None,
duration: None,
}
}
}
impl MCaptchaBuilder {
/// set defense
pub fn defense(&mut self, d: Defense) -> &mut Self {
self.defense = Some(d);
self
}
/// set duration
pub fn duration(&mut self, d: u64) -> &mut Self {
self.duration = Some(d);
self
}
/// Builds new [MCaptcha]
pub fn build(self: &mut MCaptchaBuilder) -> CaptchaResult<MCaptcha> {
if self.duration.is_none() {
Err(CaptchaError::PleaseSetValue("duration".into()))
} else if self.defense.is_none() {
Err(CaptchaError::PleaseSetValue("defense".into()))
} else if self.duration <= Some(0) {
Err(CaptchaError::CaptchaDurationZero)
} else {
let m = MCaptcha {
duration: self.duration.unwrap(),
defense: self.defense.clone().unwrap(),
visitor_threshold: self.visitor_threshold,
};
Ok(m)
}
}
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct MCaptcha {
visitor_threshold: u32,
defense: Defense,
duration: u64,
}

View File

@ -42,7 +42,8 @@ where
+ ToEnvelope<T, CacheResult>
+ ToEnvelope<T, VerifyCaptchaResult>,
X: Master,
<X as actix::Actor>::Context: ToEnvelope<X, crate::master::AddVisitor>,
<X as actix::Actor>::Context:
ToEnvelope<X, crate::master::AddVisitor> + ToEnvelope<X, crate::master::AddSite>,
{
/// utility function to get difficulty factor of site `id` and cache it
pub async fn get_pow(&self, id: String) -> Option<PoWConfig> {
@ -123,21 +124,21 @@ mod tests {
use super::System;
use super::*;
use crate::cache::HashCache;
use crate::master::embedded::counter::tests::*;
use crate::master::embedded::master::Master;
use crate::master::embedded::master::*;
use crate::master::embedded::mcaptcha::tests::*;
use crate::master::*;
const MCAPTCHA_NAME: &str = "batsense.net";
async fn boostrap_system(gc: u64) -> System<HashCache, Master> {
let master = Master::new(gc).start();
let mcaptcha = get_counter().start();
let mcaptcha = get_mcaptcha();
let pow = get_config();
let cache = HashCache::default().start();
let msg = AddSiteBuilder::default()
.id(MCAPTCHA_NAME.into())
.addr(mcaptcha.clone())
.mcaptcha(mcaptcha)
.build()
.unwrap();