Email blocklisting (#718)
* Interface complete for the email blacklisting * Everything seems to work * Neutralize language * fix clippy warnings * Add missing spaces * Added matching test * Correct primary key datatype for postgresql * Address review comments * Add placeholder when empty. Fix missing 'i'remotes/epsilon-phase/authorized-fetch
джерело
e6bdeb7c4b
коміт
f3c05dae62
@ -0,0 +1,3 @@
|
||||
-- This file should undo anything in `up.sql`
|
||||
|
||||
drop table email_blocklist;
|
@ -0,0 +1,6 @@
|
||||
-- Your SQL goes here
|
||||
CREATE TABLE email_blocklist(id SERIAL PRIMARY KEY,
|
||||
email_address TEXT UNIQUE,
|
||||
note TEXT,
|
||||
notify_user BOOLEAN DEFAULT FALSE,
|
||||
notification_text TEXT);
|
@ -0,0 +1,3 @@
|
||||
-- This file should undo anything in `up.sql`
|
||||
|
||||
drop table email_blocklist;
|
@ -0,0 +1,6 @@
|
||||
-- Your SQL goes here
|
||||
CREATE TABLE email_blocklist(id INTEGER PRIMARY KEY,
|
||||
email_address TEXT UNIQUE,
|
||||
note TEXT,
|
||||
notify_user BOOLEAN DEFAULT FALSE,
|
||||
notification_text TEXT);
|
@ -0,0 +1,142 @@
|
||||
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl, TextExpressionMethods};
|
||||
use glob::Pattern;
|
||||
|
||||
use schema::email_blocklist;
|
||||
use {Connection, Error, Result};
|
||||
|
||||
#[derive(Clone, Queryable, Identifiable)]
|
||||
#[table_name = "email_blocklist"]
|
||||
pub struct BlocklistedEmail {
|
||||
pub id: i32,
|
||||
pub email_address: String,
|
||||
pub note: String,
|
||||
pub notify_user: bool,
|
||||
pub notification_text: String,
|
||||
}
|
||||
|
||||
#[derive(Insertable, FromForm)]
|
||||
#[table_name = "email_blocklist"]
|
||||
pub struct NewBlocklistedEmail {
|
||||
pub email_address: String,
|
||||
pub note: String,
|
||||
pub notify_user: bool,
|
||||
pub notification_text: String,
|
||||
}
|
||||
|
||||
impl BlocklistedEmail {
|
||||
insert!(email_blocklist, NewBlocklistedEmail);
|
||||
get!(email_blocklist);
|
||||
find_by!(email_blocklist, find_by_id, id as i32);
|
||||
pub fn delete_entries(conn: &Connection, ids: Vec<i32>) -> Result<bool> {
|
||||
use diesel::delete;
|
||||
for i in ids {
|
||||
let be: BlocklistedEmail = BlocklistedEmail::find_by_id(&conn, i)?;
|
||||
delete(&be).execute(conn)?;
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
pub fn find_for_domain(conn: &Connection, domain: &str) -> Result<Vec<BlocklistedEmail>> {
|
||||
let effective = format!("%@{}", domain);
|
||||
email_blocklist::table
|
||||
.filter(email_blocklist::email_address.like(effective))
|
||||
.load::<BlocklistedEmail>(conn)
|
||||
.map_err(Error::from)
|
||||
}
|
||||
pub fn matches_blocklist(conn: &Connection, email: &str) -> Result<Option<BlocklistedEmail>> {
|
||||
let mut result = email_blocklist::table.load::<BlocklistedEmail>(conn)?;
|
||||
for i in result.drain(..) {
|
||||
if let Ok(x) = Pattern::new(&i.email_address) {
|
||||
if x.matches(email) {
|
||||
return Ok(Some(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
pub fn page(conn: &Connection, (min, max): (i32, i32)) -> Result<Vec<BlocklistedEmail>> {
|
||||
email_blocklist::table
|
||||
.offset(min.into())
|
||||
.limit((max - min).into())
|
||||
.load::<BlocklistedEmail>(conn)
|
||||
.map_err(Error::from)
|
||||
}
|
||||
pub fn count(conn: &Connection) -> Result<i64> {
|
||||
email_blocklist::table
|
||||
.count()
|
||||
.get_result(conn)
|
||||
.map_err(Error::from)
|
||||
}
|
||||
pub fn pattern_errors(pat: &str) -> Option<glob::PatternError> {
|
||||
let c = Pattern::new(pat);
|
||||
c.err()
|
||||
}
|
||||
pub fn new(
|
||||
conn: &Connection,
|
||||
pattern: &str,
|
||||
note: &str,
|
||||
show_notification: bool,
|
||||
notification_text: &str,
|
||||
) -> Result<BlocklistedEmail> {
|
||||
let c = NewBlocklistedEmail {
|
||||
email_address: pattern.to_owned(),
|
||||
note: note.to_owned(),
|
||||
notify_user: show_notification,
|
||||
notification_text: notification_text.to_owned(),
|
||||
};
|
||||
BlocklistedEmail::insert(conn, c)
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use diesel::Connection;
|
||||
use instance::tests as instance_tests;
|
||||
use tests::rockets;
|
||||
use Connection as Conn;
|
||||
pub(crate) fn fill_database(conn: &Conn) -> Vec<BlocklistedEmail> {
|
||||
instance_tests::fill_database(conn);
|
||||
let domainblock =
|
||||
BlocklistedEmail::new(conn, "*@bad-actor.com", "Mean spammers", false, "").unwrap();
|
||||
let userblock = BlocklistedEmail::new(
|
||||
conn,
|
||||
"spammer@lax-administration.com",
|
||||
"Decent enough domain, but this user is a problem.",
|
||||
true,
|
||||
"Stop it please",
|
||||
)
|
||||
.unwrap();
|
||||
vec![domainblock, userblock]
|
||||
}
|
||||
#[test]
|
||||
fn test_match() {
|
||||
let r = rockets();
|
||||
let conn = &*r.conn;
|
||||
conn.test_transaction::<_, (), _>(|| {
|
||||
let various = fill_database(conn);
|
||||
let match1 = "user1@bad-actor.com";
|
||||
let match2 = "spammer@lax-administration.com";
|
||||
let no_match = "happy-user@lax-administration.com";
|
||||
assert_eq!(
|
||||
BlocklistedEmail::matches_blocklist(conn, match1)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.id,
|
||||
various[0].id
|
||||
);
|
||||
assert_eq!(
|
||||
BlocklistedEmail::matches_blocklist(conn, match2)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.id,
|
||||
various[1].id
|
||||
);
|
||||
assert_eq!(
|
||||
BlocklistedEmail::matches_blocklist(conn, no_match)
|
||||
.unwrap()
|
||||
.is_none(),
|
||||
true
|
||||
);
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
@use templates::base;
|
||||
@use plume_models::blocklisted_emails::BlocklistedEmail;
|
||||
@use template_utils::*;
|
||||
@use routes::*;
|
||||
|
||||
@(ctx:BaseContext, emails: Vec<BlocklistedEmail>, page:i32, n_pages:i32)
|
||||
@:base(ctx, i18n!(ctx.1, "Blocklisted Emails"), {}, {}, {
|
||||
<h1>@i18n!(ctx.1,"Blocklisted Emails")</h1>
|
||||
@tabs(&[
|
||||
(&uri!(instance::admin).to_string(), i18n!(ctx.1, "Configuration"), false),
|
||||
(&uri!(instance::admin_instances: page = _).to_string(), i18n!(ctx.1, "Instances"), false),
|
||||
(&uri!(instance::admin_users: page = _).to_string(), i18n!(ctx.1, "Users"), false),
|
||||
(&uri!(instance::admin_email_blocklist:page=_).to_string(), i18n!(ctx.1, "Email blocklist"), true),
|
||||
])
|
||||
<form method="post" action="@uri!(instance::add_email_blocklist)">
|
||||
@(Input::new("email_address", i18n!(ctx.1, "Email address"))
|
||||
.details(i18n!(ctx.1, "The email address you wish to block. In order to block domains, you can use globbing syntax, for example '*@example.com' blocks all addresses from example.com"))
|
||||
.set_prop("minlength", 1)
|
||||
.html(ctx.1))
|
||||
@(Input::new("note", i18n!(ctx.1, "Note")).optional().html(ctx.1))
|
||||
<label for="notify_user">@i18n!(ctx.1, "Notify the user?")
|
||||
<input id="notify_user" type="checkbox" name="notify_user">
|
||||
<small>
|
||||
@i18n!(ctx.1, "Optional, shows a message to the user when they attempt to create an account with that address")
|
||||
</small>
|
||||
</label>
|
||||
@(Input::new("notification_text", i18n!(ctx.1, "Blocklisting notification"))
|
||||
.optional()
|
||||
.details(i18n!(ctx.1, "The message to be shown when the user attempts to create an account with this email address")).html(ctx.1))
|
||||
<input type="submit" value='@i18n!(ctx.1, "Add blocklisted address")'>
|
||||
</form>
|
||||
<form method="post" action="@uri!(instance::delete_email_blocklist)">
|
||||
<header>
|
||||
@if emails.is_empty() {
|
||||
<input type="submit" class="destructive" value='@i18n!(ctx.1, "Delete selected emails")'>
|
||||
} else {
|
||||
<p class="center" >@i18n!(ctx.1, "There are no blocked emails on your instance")</p>
|
||||
}
|
||||
</header>
|
||||
<div class="list">
|
||||
@for email in emails {
|
||||
<div class="card flex compact">
|
||||
<input type="checkbox" name="@email.id">
|
||||
<p class="grow">
|
||||
<strong>
|
||||
@i18n!(ctx.1, "Email address:")
|
||||
</strong> @email.email_address
|
||||
</p>
|
||||
<p class="grow">
|
||||
<strong>
|
||||
@i18n!(ctx.1, "Blocklisted for:")
|
||||
</strong> @email.note
|
||||
</p>
|
||||
|
||||
<p class="grow">
|
||||
@if email.notify_user {
|
||||
<strong>
|
||||
@i18n!(ctx.1, "Will notify them on account creation with this message:")
|
||||
</strong>
|
||||
@email.notification_text
|
||||
} else {
|
||||
@i18n!(ctx.1, "The user will be silently prevented from making an account")
|
||||
}
|
||||
</p>
|
||||
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</form>
|
||||
@paginate(ctx.1, page, n_pages)
|
||||
})
|
Завантаження…
Посилання в новій задачі