tmp22/src/master/redis/connection.rs

198 lines
6.7 KiB
Rust
Raw Normal View History

2021-06-08 18:13:01 +05:30
/*
* mCaptcha - A proof of work based DoS protection system
* Copyright © 2021 Aravinth Manivannan <realravinth@batsense.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* 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/>.
*/
use redis::Value;
use crate::errors::*;
2021-06-09 14:03:19 +05:30
use crate::master::messages::{AddSite, AddVisitor};
use crate::master::AddVisitorResult;
use crate::master::CreateMCaptcha;
use crate::redis::Redis;
use crate::redis::RedisConfig;
use crate::redis::RedisConnection;
2021-06-08 18:13:01 +05:30
2021-06-09 14:03:19 +05:30
/// Redis instance with mCaptcha Redis module loaded
pub struct MCaptchaRedis(Redis);
2021-06-09 14:18:31 +05:30
/// Connection to Redis instance with mCaptcha Redis module loaded
2021-06-09 14:03:19 +05:30
pub struct MCaptchaRedisConnection(RedisConnection);
2021-06-08 18:13:01 +05:30
const GET: &str = "MCAPTCHA_CACHE.GET";
const ADD_VISITOR: &str = "MCAPTCHA_CACHE.ADD_VISITOR";
const DEL: &str = "MCAPTCHA_CACHE.DELETE_CAPTCHA";
const ADD_CAPTCHA: &str = "MCAPTCHA_CACHE.ADD_CAPTCHA";
const CAPTCHA_EXISTS: &str = "MCAPTCHA_CACHE.CAPTCHA_EXISTS";
const MODULE_NAME: &str = "mcaptcha_cahce";
2021-06-09 14:03:19 +05:30
impl MCaptchaRedis {
2021-06-09 14:18:31 +05:30
/// Get new [MCaptchaRedis]. Use this when executing commands that are
/// only supported by mCaptcha Redis module. Internally, when object
/// is created, checks are performed to check if the module is loaded and if
/// the required commands are available
2021-06-09 14:03:19 +05:30
pub async fn new(redis: RedisConfig) -> CaptchaResult<Self> {
let redis = Redis::new(redis).await?;
let m = MCaptchaRedis(redis);
m.get_client().is_module_loaded().await?;
Ok(m)
}
2021-06-09 14:18:31 +05:30
/// Get connection to a Redis instance with mCaptcha Redis module loaded
///
/// Uses interior mutability so look out for panics!
2021-06-09 14:03:19 +05:30
pub fn get_client(&self) -> MCaptchaRedisConnection {
MCaptchaRedisConnection(self.0.get_client())
}
2021-06-08 18:13:01 +05:30
}
2021-06-09 14:03:19 +05:30
impl MCaptchaRedisConnection {
2021-06-09 14:18:31 +05:30
async fn is_module_loaded(&self) -> CaptchaResult<()> {
2021-06-09 14:03:19 +05:30
let modules: Vec<Vec<String>> = self
.0
.exec(redis::cmd("MODULE").arg(&["LIST"]))
.await
.unwrap();
2021-06-08 18:13:01 +05:30
for list in modules.iter() {
match list.iter().find(|module| module.as_str() == MODULE_NAME) {
2021-06-08 20:15:02 +05:30
Some(_) => (),
None => return Err(CaptchaError::MCaptchaRedisModuleIsNotLoaded),
2021-06-08 18:13:01 +05:30
}
}
let commands = vec![ADD_VISITOR, ADD_CAPTCHA, DEL, CAPTCHA_EXISTS, GET];
for cmd in commands.iter() {
2021-06-09 14:03:19 +05:30
match self
.0
.exec(redis::cmd("COMMAND").arg(&["INFO", cmd]))
.await
.unwrap()
{
2021-06-08 18:13:01 +05:30
Value::Bulk(mut val) => {
2021-06-08 20:15:02 +05:30
match val.pop() {
Some(Value::Nil) => {
return Err(CaptchaError::MCaptchaRediSModuleCommandNotFound(
cmd.to_string(),
))
}
_ => (),
2021-06-08 18:13:01 +05:30
};
}
2021-06-08 20:15:02 +05:30
_ => (),
2021-06-08 18:13:01 +05:30
};
}
2021-06-08 20:15:02 +05:30
Ok(())
2021-06-08 18:13:01 +05:30
}
2021-06-09 14:18:31 +05:30
/// Add visitor
2021-06-08 18:13:01 +05:30
pub async fn add_visitor(&self, msg: AddVisitor) -> CaptchaResult<Option<AddVisitorResult>> {
2021-06-09 14:03:19 +05:30
let res: String = self.0.exec(redis::cmd(ADD_VISITOR).arg(&[msg.0])).await?;
2021-06-08 18:13:01 +05:30
let res: AddVisitorResult = serde_json::from_str(&res).unwrap();
Ok(Some(res))
}
2021-06-09 14:18:31 +05:30
/// Register new mCaptcha with Redis
2021-06-08 18:13:01 +05:30
pub async fn add_mcaptcha(&self, msg: AddSite) -> CaptchaResult<()> {
let name = msg.id;
let captcha: CreateMCaptcha = msg.mcaptcha.into();
let payload = serde_json::to_string(&captcha).unwrap();
2021-06-09 14:03:19 +05:30
self.0
.exec(redis::cmd(ADD_CAPTCHA).arg(&[name, payload]))
.await?;
2021-06-08 18:13:01 +05:30
Ok(())
}
2021-06-09 14:18:31 +05:30
/// Check if an mCaptcha object is available in Redis
2021-06-08 18:13:01 +05:30
pub async fn check_captcha_exists(&self, captcha: &str) -> CaptchaResult<bool> {
2021-06-09 14:03:19 +05:30
let exists: usize = self
.0
.exec(redis::cmd(CAPTCHA_EXISTS).arg(&[captcha]))
.await?;
2021-06-08 18:13:01 +05:30
if exists == 1 {
Ok(false)
} else if exists == 0 {
Ok(true)
} else {
log::error!(
"mCaptcha redis module responded with {} when for {}",
exists,
CAPTCHA_EXISTS
);
Err(CaptchaError::MCaptchaRedisModuleError)
}
}
2021-06-09 14:18:31 +05:30
/// Delete an mCaptcha object from Redis
2021-06-08 18:13:01 +05:30
pub async fn delete_captcha(&self, captcha: &str) -> CaptchaResult<()> {
2021-06-09 14:03:19 +05:30
self.0.exec(redis::cmd(DEL).arg(&[captcha])).await?;
2021-06-08 18:13:01 +05:30
Ok(())
}
2021-06-09 14:18:31 +05:30
/// Get number of visitors of an mCaptcha object from Redis
2021-06-08 18:13:01 +05:30
pub async fn get_visitors(&self, captcha: &str) -> CaptchaResult<usize> {
2021-06-09 14:03:19 +05:30
let visitors: usize = self.0.exec(redis::cmd(GET).arg(&[captcha])).await?;
2021-06-08 18:13:01 +05:30
Ok(visitors)
}
}
2021-06-08 20:15:02 +05:30
#[cfg(test)]
2021-06-08 20:43:47 +05:30
pub mod tests {
2021-06-08 20:15:02 +05:30
use super::*;
use crate::master::embedded::counter::tests::get_mcaptcha;
2021-06-09 14:03:19 +05:30
use crate::redis::*;
2021-06-08 20:15:02 +05:30
const CAPTCHA_NAME: &str = "REDIS_CAPTCHA_TEST";
2021-06-08 21:22:12 +05:30
const REDIS_URL: &str = "redis://127.0.1.1/";
2021-06-08 20:15:02 +05:30
#[actix_rt::test]
async fn redis_master_works() {
2021-06-09 14:03:19 +05:30
let redis = Redis::new(RedisConfig::Single(REDIS_URL.into()))
.await
.unwrap();
let r = MCaptchaRedis(redis);
let r = r.get_client();
2021-06-08 20:15:02 +05:30
{
let _ = r.delete_captcha(CAPTCHA_NAME).await;
}
assert!(r.is_module_loaded().await.is_ok());
assert!(!r.check_captcha_exists(CAPTCHA_NAME).await.unwrap());
let add_mcaptcha_msg = AddSite {
id: CAPTCHA_NAME.into(),
mcaptcha: get_mcaptcha(),
};
assert!(r.add_mcaptcha(add_mcaptcha_msg).await.is_ok());
assert!(r.check_captcha_exists(CAPTCHA_NAME).await.unwrap());
let add_visitor_msg = AddVisitor(CAPTCHA_NAME.into());
assert!(r.add_visitor(add_visitor_msg).await.is_ok());
let visitors = r.get_visitors(CAPTCHA_NAME).await.unwrap();
assert_eq!(visitors, 1);
let add_visitor_msg = AddVisitor(CAPTCHA_NAME.into());
assert!(r.add_visitor(add_visitor_msg).await.is_ok());
let visitors = r.get_visitors(CAPTCHA_NAME).await.unwrap();
assert_eq!(visitors, 2);
assert!(r.delete_captcha(CAPTCHA_NAME).await.is_ok());
}
}