Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package forgejo-guardian for openSUSE:Factory checked in at 2025-01-29 16:18:23 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/forgejo-guardian (Old) and /work/SRC/openSUSE:Factory/.forgejo-guardian.new.2316 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "forgejo-guardian" Wed Jan 29 16:18:23 2025 rev:2 rq:1241173 version:0.5.0 Changes: -------- --- /work/SRC/openSUSE:Factory/forgejo-guardian/forgejo-guardian.changes 2025-01-23 18:03:03.458301207 +0100 +++ /work/SRC/openSUSE:Factory/.forgejo-guardian.new.2316/forgejo-guardian.changes 2025-01-29 16:19:02.665032992 +0100 @@ -1,0 +2,11 @@ +Tue Jan 28 20:15:49 UTC 2025 - Richard Rahl <rra...@opensuse.org> + +- update to 0.5.0: + * Ability to check user tokens and oauth2 apps + * The minimum value for inactive.req_limit changed to 4 + * Ability to enter seconds in the interval without s suffix + * Make expressions.interval suffixably + * Guardian checks all instance users when expressions.only_new_users is false + * Prevent async deadlock when Telegram is disabled + +------------------------------------------------------------------- Old: ---- forgejo-guardian-0.4.1.obscpio New: ---- forgejo-guardian-0.5.0.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ forgejo-guardian.spec ++++++ --- /var/tmp/diff_new_pack.L9FkpA/_old 2025-01-29 16:19:03.897084084 +0100 +++ /var/tmp/diff_new_pack.L9FkpA/_new 2025-01-29 16:19:03.897084084 +0100 @@ -17,7 +17,7 @@ Name: forgejo-guardian -Version: 0.4.1 +Version: 0.5.0 Release: 0 Summary: Simple Forgejo instance guardian License: AGPL-3.0-or-later ++++++ _service ++++++ --- /var/tmp/diff_new_pack.L9FkpA/_old 2025-01-29 16:19:03.921085079 +0100 +++ /var/tmp/diff_new_pack.L9FkpA/_new 2025-01-29 16:19:03.925085245 +0100 @@ -3,7 +3,7 @@ <service name="obs_scm" mode="manual"> <param name="scm">git</param> <param name="url">https://git.4rs.nl/awiteb/forgejo-guardian</param> - <param name="revision">refs/tags/v0.4.1</param> + <param name="revision">refs/tags/v0.5.0</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> </service> ++++++ forgejo-guardian-0.4.1.obscpio -> forgejo-guardian-0.5.0.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-guardian-0.4.1/CHANGELOG.md new/forgejo-guardian-0.5.0/CHANGELOG.md --- old/forgejo-guardian-0.4.1/CHANGELOG.md 2025-01-22 07:46:25.000000000 +0100 +++ new/forgejo-guardian-0.5.0/CHANGELOG.md 2025-01-28 20:22:43.000000000 +0100 @@ -6,6 +6,16 @@ ## unreleased ### Added +- Ability to check user tokens and oauth2 apps ([**#25**](https://git.4rs.nl/awiteb/forgejo-guardian/issues/25)) ([`1e90760`](https://git.4rs.nl/awiteb/forgejo-guardian/commit/1e907609cd9fae24e58cbe9eab99cc4b88459cb3)) + - **BC**: The minimum value for `inactive.req_limit` changed to 4 +- Ability to enter seconds in the interval without `s` suffix ([**#28**](https://git.4rs.nl/awiteb/forgejo-guardian/issues/28)) ([`c9dfc6e`](https://git.4rs.nl/awiteb/forgejo-guardian/commit/c9dfc6e57acdfbdbc2485d729ce24edcab292224)) +- Make `expressions.interval` suffixably ([**#29**](https://git.4rs.nl/awiteb/forgejo-guardian/issues/29)) ([`e32aca7`](https://git.4rs.nl/awiteb/forgejo-guardian/commit/e32aca7164669532ca08ee356fc01aaf6185aa67)) +### Fixed +- Guardian checks all instance users when `expressions.only_new_users` is `false` ([**#27**](https://git.4rs.nl/awiteb/forgejo-guardian/issues/27)) ([`cf05a68`](https://git.4rs.nl/awiteb/forgejo-guardian/commit/cf05a68c22f1eeffc3308856c7117ff6d82855da)) +- Prevent async deadlock when Telegram is disabled ([**#31**](https://git.4rs.nl/awiteb/forgejo-guardian/issues/31)) ([`2ee7849`](https://git.4rs.nl/awiteb/forgejo-guardian/commit/2ee784916305d32705d6a667ce1979c47f67874f)) + +## [0.4.1](https://git.4rs.nl/awiteb/forgejo-guardian/compare/v0.4.0..v0.4.1) - 2025-01-22 +### Added - Add support for including and excluding users ([**#22**](https://git.4rs.nl/awiteb/forgejo-guardian/issues/22)) ([`f07fdab`](https://git.4rs.nl/awiteb/forgejo-guardian/commit/f07fdaba7e9a37b87848e6d0bbb6b639c84cfd95)) ### Fixed - Check for the user activities for more than last 365 days ([`e41b9b3`](https://git.4rs.nl/awiteb/forgejo-guardian/commit/e41b9b36f67ff465731a20c1baaca9a9e6440bd0)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-guardian-0.4.1/Cargo.lock new/forgejo-guardian-0.5.0/Cargo.lock --- old/forgejo-guardian-0.4.1/Cargo.lock 2025-01-22 07:46:25.000000000 +0100 +++ new/forgejo-guardian-0.5.0/Cargo.lock 2025-01-28 20:22:43.000000000 +0100 @@ -332,7 +332,7 @@ [[package]] name = "forgejo-guardian" -version = "0.4.1" +version = "0.5.0" dependencies = [ "chrono", "regex", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-guardian-0.4.1/Cargo.toml new/forgejo-guardian-0.5.0/Cargo.toml --- old/forgejo-guardian-0.4.1/Cargo.toml 2025-01-22 07:46:25.000000000 +0100 +++ new/forgejo-guardian-0.5.0/Cargo.toml 2025-01-28 20:22:43.000000000 +0100 @@ -1,7 +1,7 @@ [package] name = "forgejo-guardian" description = "Simple Forgejo instance guardian, banning users and alerting admins based on certain regular expressions" -version = "0.4.1" +version = "0.5.0" edition = "2021" authors = ["Awiteb <a...@4rs.nl>"] repository = "https://git.4rs.nl/awiteb/forgejo-guardian" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-guardian-0.4.1/README.md new/forgejo-guardian-0.5.0/README.md --- old/forgejo-guardian-0.4.1/README.md 2025-01-22 07:46:25.000000000 +0100 +++ new/forgejo-guardian-0.5.0/README.md 2025-01-28 20:22:43.000000000 +0100 @@ -66,7 +66,7 @@ ```yaml services: forgejo-guardian: - image: git.4rs.nl/awiteb/forgejo-guardian:0.4 + image: git.4rs.nl/awiteb/forgejo-guardian:0.5 volumes: - ./forgejo-guardian.toml:/app/forgejo-guardian.toml:ro ``` @@ -89,7 +89,7 @@ #### Without building the image ```sh -docker run --rm -d -v $PWD/forgejo-guardian.toml:/app/forgejo-guardian.toml:ro git.4rs.nl/awiteb/forgejo-guardian:0.4 +docker run --rm -d -v $PWD/forgejo-guardian.toml:/app/forgejo-guardian.toml:ro git.4rs.nl/awiteb/forgejo-guardian:0.5 ``` ## Installation @@ -176,12 +176,20 @@ Inactive users configuration section, with the following fields: +> [!NOTE] +> The field may start with a version, this version is the required Forgejo +> version, so you can use this version or later + - `enabled`: Enable the cleanup of inactive users, inactive feature need `read:user` scope (default: `false`) - `exclude`: List of usernames to exclude from the cleanup (default: `[]`) - `source_id`: List of source IDs to only consider users from (default: `[]`) - `source_id_exclude`: List of source IDs to exclude users from (default: `[]`) +- **v10.0.1** `check_tokens`: Check if the user has tokens, if true the user + will not be considered (default: `true`) +- **v10.0.1** `check_oauth2`: Check if the user has OAuth2 applications, if + true the user will not be considered (default: `true`) - `days`: The number of days that a new user is given to become active. (default: `30`) -- `req_limit`: Maximum number of requests to send to the Forgejo instance within each interval (default: `200`) (Minimum: `2`) +- `req_limit`: Maximum number of requests to send to the Forgejo instance within each interval (default: `200`) (Minimum: `4`) - `req_interval`: Time interval to pause after reaching the `req_limit` (default: `10m`) - `interval`: Time Interval to check for inactive users (default: `7d`) @@ -214,8 +222,10 @@ Expressions configuration section, with the following fields: - `only_new_users`: If set to `true`, the guardian will only check the new users, and not the existing ones (default: `false`) -- `interval`: Interval to check for new users in seconds (default: `300`) +- `interval`: Interval to check for new users in seconds (default: `300s`) - `limit`: Limit of users to fetch in each interval (default: `100`) +- `req_limit`: Maximum number of requests to send to the Forgejo instance within each interval (default: `200`) (Minimum: `1`) * +- `req_interval`: Time interval to pause after reaching the `req_limit` (default: `10m`) * - `ban_alert`: Send a notification when a user is banned (default: `false`) - `ban_action`: The action to take when a user is banned, can be one of the following: - `purge` (default): Forcibly delete user and any repositories, organizations, and @@ -227,6 +237,15 @@ - `ban`: Regular expressions to match against to ban the user - `sus`: Regular expressions to match against to alert the admins +The `expressions.interval` and `expressions.req_interval` have the following suffixes: + +- `s`: Seconds +- `m`: Minutes +- `h`: Hours +- `d`: Days + +*: Only for checking old users, if `only_new_users` is set to `true`, the guardian will not use these values. + `ban` and `sus` are tables, and each one have the following fields: - `enabled`: Enable the expressions (default: enabled if the section is present, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-guardian-0.4.1/docker/Dockerfile new/forgejo-guardian-0.5.0/docker/Dockerfile --- old/forgejo-guardian-0.4.1/docker/Dockerfile 2025-01-22 07:46:25.000000000 +0100 +++ new/forgejo-guardian-0.5.0/docker/Dockerfile 2025-01-28 20:22:43.000000000 +0100 @@ -3,7 +3,7 @@ WORKDIR /app RUN apk add --no-cache curl -RUN curl https://git.4rs.nl/awiteb/forgejo-guardian/releases/download/v0.4.1/forgejo-guardian-v0.4.1-x86_64-linux-musl --output forgejo-guardian +RUN curl https://git.4rs.nl/awiteb/forgejo-guardian/releases/download/v0.5.0/forgejo-guardian-v0.5.0-x86_64-linux-musl --output forgejo-guardian RUN chmod +x forgejo-guardian ENTRYPOINT ["./forgejo-guardian"] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-guardian-0.4.1/src/config/defaults.rs new/forgejo-guardian-0.5.0/src/config/defaults.rs --- old/forgejo-guardian-0.4.1/src/config/defaults.rs 2025-01-22 07:46:25.000000000 +0100 +++ new/forgejo-guardian-0.5.0/src/config/defaults.rs 2025-01-28 20:22:43.000000000 +0100 @@ -23,6 +23,14 @@ pub const fn ban_action() -> BanAction { BanAction::Purge } + + pub const fn req_limit() -> u32 { + 200 + } + + pub const fn req_interval() -> u32 { + 10 * 60 + } } /// Default configuration for inactive section. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-guardian-0.4.1/src/config/deserializers.rs new/forgejo-guardian-0.5.0/src/config/deserializers.rs --- old/forgejo-guardian-0.4.1/src/config/deserializers.rs 2025-01-22 07:46:25.000000000 +0100 +++ new/forgejo-guardian-0.5.0/src/config/deserializers.rs 2025-01-28 20:22:43.000000000 +0100 @@ -121,8 +121,19 @@ where D: de::Deserializer<'de>, { - let interval = String::deserialize(des) - .map_err(|_| de::Error::custom("Expected a suffixed interval, e.g. 1s, 2m"))?; + let toml_value = Value::deserialize(des)?; + + if let Value::Integer(interval) = toml_value { + return u32::try_from(interval) + .map_err(|_| de::Error::custom("It is a negative number, it must be positive")); + } + + let Value::String(interval) = toml_value else { + return Err(de::Error::custom( + "Expected a suffixed interval, e.g. 1s, 2m", + )); + }; + if interval.chars().count() < 2 { return Err(de::Error::custom(format!( "Expected a suffixed interval, e.g. 1s, 2m. found {interval}" @@ -142,10 +153,10 @@ })?; let suffix = interval.chars().last().expect("the length more than 2"); let interval = match suffix { - 's' => number, - 'm' => number * 60, - 'h' => number * 60 * 60, - 'd' => number * 24 * 60 * 60, + 's' => Some(number), + 'm' => number.checked_mul(60), + 'h' => number.checked_mul(60 * 60), + 'd' => number.checked_mul(24 * 60 * 60), _ => { return Err(de::Error::custom(format!( "Unknown suffix `{suffix}`, expected s, m, h, d" @@ -153,7 +164,9 @@ } }; - Ok(interval) + interval.ok_or_else(|| { + de::Error::custom("The interval is too large, the maximum value is 49710 days.") + }) } /// Deserialize the deserializer into `T` then check if the value is greater diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-guardian-0.4.1/src/config/mod.rs new/forgejo-guardian-0.5.0/src/config/mod.rs --- old/forgejo-guardian-0.4.1/src/config/mod.rs 2025-01-22 07:46:25.000000000 +0100 +++ new/forgejo-guardian-0.5.0/src/config/mod.rs 2025-01-28 20:22:43.000000000 +0100 @@ -48,12 +48,19 @@ /// Source ID to exclude #[serde(default)] pub source_id_exclude: Vec<u32>, + /// Check if the user has tokens, if true the user will not be considered + #[serde(default = "defaults::bool_true")] + pub check_tokens: bool, + /// Check if the user has OAuth2 applications, if true the user will not be + /// considered + #[serde(default = "defaults::bool_true")] + pub check_oauth2: bool, /// Number of inactive days to consider #[serde(default = "defaults::inactive::days")] pub days: u64, /// Number of requests to send #[serde(default = "defaults::inactive::req_limit")] - #[serde(deserialize_with = "deserializers::unsigned_minimum::<_, _, 2>")] + #[serde(deserialize_with = "deserializers::unsigned_minimum::<_, _, 4>")] pub req_limit: u16, /// Time interval in seconds for the request limit #[serde( @@ -171,11 +178,26 @@ #[serde(default)] pub only_new_users: bool, /// Interval to check for new users in seconds - #[serde(default = "defaults::expressions::interval")] + #[serde( + default = "defaults::expressions::interval", + deserialize_with = "deserializers::suffix_interval" + )] pub interval: u32, /// Limit of users to fetch in each interval #[serde(default = "defaults::expressions::limit")] pub limit: u32, + /// Maximum number of requests to send + #[serde( + default = "defaults::expressions::req_limit", + deserialize_with = "deserializers::unsigned_minimum::<_, _, 1>" + )] + pub req_limit: u32, + /// Interval when hitting the request limit + #[serde( + default = "defaults::expressions::req_interval", + deserialize_with = "deserializers::suffix_interval" + )] + pub req_interval: u32, /// Action to take when banning a user #[serde(default = "defaults::expressions::ban_action")] pub ban_action: BanAction, @@ -267,6 +289,8 @@ exclude: Vec::new(), source_id: Vec::new(), source_id_exclude: Vec::new(), + check_tokens: true, + check_oauth2: true, days: defaults::inactive::days(), req_limit: defaults::inactive::req_limit(), req_interval: defaults::inactive::req_interval(), @@ -282,6 +306,8 @@ ban_alert: false, interval: defaults::expressions::interval(), limit: defaults::expressions::limit(), + req_limit: defaults::expressions::req_limit(), + req_interval: defaults::expressions::req_interval(), ban_action: defaults::expressions::ban_action(), ban: Expr::default(), sus: Expr::default(), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-guardian-0.4.1/src/forgejo_api/activity_feed.rs new/forgejo-guardian-0.5.0/src/forgejo_api/activity_feed.rs --- old/forgejo-guardian-0.4.1/src/forgejo_api/activity_feed.rs 2025-01-22 07:46:25.000000000 +0100 +++ new/forgejo-guardian-0.5.0/src/forgejo_api/activity_feed.rs 2025-01-28 20:22:43.000000000 +0100 @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2024-2025 Awiteb <a...@4rs.nl> + use reqwest::{Client, Method}; use url::Url; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-guardian-0.4.1/src/forgejo_api/mod.rs new/forgejo-guardian-0.5.0/src/forgejo_api/mod.rs --- old/forgejo-guardian-0.4.1/src/forgejo_api/mod.rs 2025-01-22 07:46:25.000000000 +0100 +++ new/forgejo-guardian-0.5.0/src/forgejo_api/mod.rs 2025-01-28 20:22:43.000000000 +0100 @@ -6,12 +6,14 @@ mod activity_feed; mod ban_user; mod get_users; +mod tokens; mod user; pub use activity_feed::*; pub use ban_user::*; pub use get_users::*; use reqwest::{Method, Request}; +pub use tokens::*; pub use user::*; /// Build a request with the given method, instance, token and endpoint. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-guardian-0.4.1/src/forgejo_api/tokens.rs new/forgejo-guardian-0.5.0/src/forgejo_api/tokens.rs --- old/forgejo-guardian-0.4.1/src/forgejo_api/tokens.rs 1970-01-01 01:00:00.000000000 +0100 +++ new/forgejo-guardian-0.5.0/src/forgejo_api/tokens.rs 2025-01-28 20:22:43.000000000 +0100 @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2024-2025 Awiteb <a...@4rs.nl> + +use reqwest::{Client, Method}; +use url::Url; + +use crate::{ + error::{GuardError, GuardResult}, + forgejo_api, +}; + +/// Returns whether the tokens is empty. +pub async fn is_empty_tokens( + client: &Client, + instance: &Url, + token: &str, + username: &str, +) -> GuardResult<bool> { + let req = forgejo_api::build_request( + Method::GET, + instance, + token, + &format!("/api/v1/users/{username}/tokens"), + ); + let url = req.url().clone(); + let res = client.execute(req).await?; + + if !res.status().is_success() { + return Err(GuardError::InvalidForgejoResponse( + format!("Status code: {status}", status = res.status()), + url, + )); + } + + tracing::debug!("Get tokens response: {res:?}"); + + Ok(res.text().await.unwrap_or_default().trim() == "[]") +} + +/// Returns whether the apps is empty +pub async fn is_empty_apps( + client: &Client, + instance: &Url, + token: &str, + username: &str, +) -> GuardResult<bool> { + let req = forgejo_api::build_request( + Method::GET, + instance, + token, + &format!("/api/v1/user/applications/oauth2?sudo={username}"), + ); + let url = req.url().clone(); + let res = client.execute(req).await?; + + if !res.status().is_success() { + return Err(GuardError::InvalidForgejoResponse( + format!("Status code: {status}", status = res.status()), + url, + )); + } + + tracing::debug!("Get apps response: {res:?}"); + + Ok(res.text().await.unwrap_or_default().trim() == "[]") +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-guardian-0.4.1/src/inactive_users.rs new/forgejo-guardian-0.5.0/src/inactive_users.rs --- old/forgejo-guardian-0.4.1/src/inactive_users.rs 2025-01-22 07:46:25.000000000 +0100 +++ new/forgejo-guardian-0.5.0/src/inactive_users.rs 2025-01-28 20:22:43.000000000 +0100 @@ -8,6 +8,7 @@ use reqwest::Client; use tokio_util::sync::CancellationToken; +use url::Url; use crate::{ config::{BanAction, Config}, @@ -16,6 +17,65 @@ const LIMIT: u32 = 30; +/// Returns true if the user has no tokens or `is_enabled` is false. +/// +/// If there is an error while fetching the tokens, it will return false. +async fn is_empty_tokens( + client: &Client, + instance: &Url, + token: &str, + username: &str, + is_enabled: bool, +) -> bool { + if !is_enabled { + return true; + } + + match forgejo_api::is_empty_tokens(client, instance, token, username).await { + Ok(value) => value, + Err(err) => { + tracing::error!("Error while get user `@{}` tokens: {err}", username); + false + } + } +} + +/// Returns true if the user has no apps or `is_enabled` is false. +/// +/// If there is an error while fetching the apps, it will return false. +async fn is_empty_apps( + client: &Client, + instance: &Url, + token: &str, + username: &str, + is_enabled: bool, +) -> bool { + if !is_enabled { + return true; + } + + match forgejo_api::is_empty_apps(client, instance, token, username).await { + Ok(value) => value, + Err(err) => { + tracing::error!("Error while get user `@{}` tokens: {err}", username); + false + } + } +} + +/// Returns true if the tokens and apps are empty +async fn is_empty_tokens_and_apps( + client: &Client, + instance: &Url, + token: &str, + username: &str, + tokens_enabled: bool, + apps_enabled: bool, +) -> bool { + is_empty_tokens(client, instance, token, username, tokens_enabled).await + && is_empty_apps(client, instance, token, username, apps_enabled).await +} + /// Check if the user is inactive. async fn check_user(req_client: &Client, config: &Config, user: ForgejoUser) -> usize { if user.is_admin @@ -33,15 +93,25 @@ return 0; } - match forgejo_api::is_empty_feeds( - req_client, - &config.forgejo.instance, - &config.forgejo.token, - &user.username, - ) - .await - { - Ok(true) => { + match ( + forgejo_api::is_empty_feeds( + req_client, + &config.forgejo.instance, + &config.forgejo.token, + &user.username, + ) + .await, + is_empty_tokens_and_apps( + req_client, + &config.forgejo.instance, + &config.forgejo.token, + &user.username, + config.inactive.check_tokens, + config.inactive.check_oauth2, + ) + .await, + ) { + (Ok(true), true) => { tracing::info!("User `@{}` is inactive.", user.username); if !config.dry_run { if let Err(err) = forgejo_api::ban_user( @@ -55,16 +125,19 @@ { tracing::error!("Error while ban inactive user `@{}`: {err}", user.username); } - return 2; // heatmap and purge request} + // activity feed, purge request and tokens (if sended) + return 2 + + usize::from(config.inactive.check_tokens) + + usize::from(config.inactive.check_oauth2); } } - Err(err) => { + (Err(err), ..) => { tracing::error!("{err}"); } _ => {} } - 1 // only heatmap request + 1 // only activity feed request } /// Check all the instance users and delete the inactive ones. @@ -131,8 +204,7 @@ } }; for user in users { - // +2 Because the next check need 1~2 requests - if (reqs + 2) > config.inactive.req_limit.into() { + if (reqs + 4) > config.inactive.req_limit.into() { if wait_interval().await { tracing::warn!("Inactive users checker stopped while checking users."); break 'main_loop; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-guardian-0.4.1/src/main.rs new/forgejo-guardian-0.5.0/src/main.rs --- old/forgejo-guardian-0.4.1/src/main.rs 2025-01-22 07:46:25.000000000 +0100 +++ new/forgejo-guardian-0.5.0/src/main.rs 2025-01-28 20:22:43.000000000 +0100 @@ -116,6 +116,13 @@ sus_sender, ban_sender, )); + + if !config.expressions.only_new_users { + tokio::spawn(users_fetcher::old_users( + Arc::clone(&config), + cancellation_token.clone(), + )); + } } if let Some(telegram) = config.telegram.is_enabled() { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-guardian-0.4.1/src/traits.rs new/forgejo-guardian-0.5.0/src/traits.rs --- old/forgejo-guardian-0.4.1/src/traits.rs 2025-01-22 07:46:25.000000000 +0100 +++ new/forgejo-guardian-0.5.0/src/traits.rs 2025-01-28 20:22:43.000000000 +0100 @@ -14,6 +14,10 @@ impl ExprChecker for Expr { fn is_match<'a>(&'a self, user: &ForgejoUser) -> Option<RegexReason> { + if !self.enabled { + return None; + } + let one_of = |hay: &str, exprs: &'a Vec<RegexReason>| { // Join the user bio into a single line // ref: https://git.4rs.nl/awiteb/forgejo-guardian/issues/2 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-guardian-0.4.1/src/users_fetcher.rs new/forgejo-guardian-0.5.0/src/users_fetcher.rs --- old/forgejo-guardian-0.4.1/src/users_fetcher.rs 2025-01-22 07:46:25.000000000 +0100 +++ new/forgejo-guardian-0.5.0/src/users_fetcher.rs 2025-01-28 20:22:43.000000000 +0100 @@ -43,22 +43,22 @@ .collect()) } -/// Check if ban or suspect a new user +/// Check if ban or suspect a new user, returns `true` if the ban request sended async fn check_new_user( user: ForgejoUser, request_client: &reqwest::Client, config: &Config, - sus_sender: &Sender<(ForgejoUser, RegexReason)>, - ban_sender: &Sender<(ForgejoUser, RegexReason)>, -) { + sus_sender: Option<&Sender<(ForgejoUser, RegexReason)>>, + ban_sender: Option<&Sender<(ForgejoUser, RegexReason)>>, +) -> bool { if let Some(re) = config.expressions.ban.is_match(&user) { tracing::info!("@{} has been banned because `{re}`", user.username); if config.dry_run { // If it's a dry run, we don't need to ban the user - if config.expressions.ban_alert { - ban_sender.send((user, re)).await.ok(); + if config.expressions.ban_alert && ban_sender.is_some() { + ban_sender.unwrap().send((user, re)).await.ok(); } - return; + return false; } if let Err(err) = forgejo_api::ban_user( @@ -71,13 +71,15 @@ .await { tracing::error!("Error while banning a user: {err}"); - } else if config.expressions.ban_alert { - ban_sender.send((user, re)).await.ok(); + } else if config.expressions.ban_alert && ban_sender.is_some() { + ban_sender.unwrap().send((user, re)).await.ok(); } - } else if let Some(re) = config.expressions.sus.is_match(&user) { + return true; + } else if let Some(re) = sus_sender.and(config.expressions.sus.is_match(&user)) { tracing::info!("@{} has been suspected because `{re}`", user.username); - sus_sender.send((user, re)).await.ok(); + sus_sender.unwrap().send((user, re)).await.ok(); } + false } /// Check for new users and send the suspected users to the channel and ban the @@ -107,12 +109,27 @@ last_user_id.store(uid, Ordering::Relaxed); } - if config.expressions.only_new_users && is_first_fetch { + if is_first_fetch { return; } for user in new_users { - check_new_user(user, &request_client, &config, &sus_sender, &ban_sender).await; + check_new_user( + user, + &request_client, + &config, + config + .telegram + .is_enabled() + .is_some() + .then_some(&sus_sender), + config + .telegram + .is_enabled() + .is_some() + .then_some(&ban_sender), + ) + .await; } } Err(err) => { @@ -151,3 +168,71 @@ }; } } + +/// Check for old users and ban them if they match the ban expressions. This +/// will not sned any alerts +pub async fn old_users(config: Arc<Config>, cancellation_token: CancellationToken) { + tracing::info!("Starting old users fetcher"); + + let wait_interval = || { + async { + tracing::debug!( + "Reached the request limit for old users checker. Waiting for {} seconds.", + config.expressions.req_interval + ); + tokio::select! { + _ = tokio::time::sleep(Duration::from_secs(config.expressions.req_interval.into())) => false, + _ = cancellation_token.cancelled() => true, + } + } + }; + + let client = reqwest::Client::new(); + let mut reqs = 0; + let mut page = 1; + + 'main_loop: loop { + // Enter the block if we cancelled, so will break + if reqs >= config.expressions.req_limit || cancellation_token.is_cancelled() { + if wait_interval().await { + break; + } + reqs = 0 + } + reqs += 1; + + let Ok(users) = forgejo_api::get_users( + &client, + &config.forgejo.instance, + &config.forgejo.token, + config.expressions.limit, + page, + "oldest", + ) + .await + else { + tracing::error!("Falid to fetch old users"); + continue; + }; + + if users.is_empty() { + tracing::info!("No more old users to check, all instance users are checked."); + break; + } + + for user in users { + if reqs >= config.expressions.req_limit || cancellation_token.is_cancelled() { + if wait_interval().await { + break 'main_loop; + } + reqs = 0; + } + + if check_new_user(user, &client, &config, None, None).await { + reqs += 1; + } + } + + page += 1; + } +} ++++++ forgejo-guardian.obsinfo ++++++ --- /var/tmp/diff_new_pack.L9FkpA/_old 2025-01-29 16:19:04.037089890 +0100 +++ /var/tmp/diff_new_pack.L9FkpA/_new 2025-01-29 16:19:04.041090056 +0100 @@ -1,5 +1,5 @@ name: forgejo-guardian -version: 0.4.1 -mtime: 1737528385 -commit: 1e76bd0a05771dfe53d0e4d15f68e7657a705334 +version: 0.5.0 +mtime: 1738092163 +commit: e82f4a1e1f195f0778eb52ee6a87181f4bbc60e4 ++++++ vendor.tar.zst ++++++ /work/SRC/openSUSE:Factory/forgejo-guardian/vendor.tar.zst /work/SRC/openSUSE:Factory/.forgejo-guardian.new.2316/vendor.tar.zst differ: char 7, line 1