Skip to content

Commit 3ef6cce

Browse files
committed
Merge branch 'dev' of github.com:DefGuard/defguard into dev
2 parents 3cf89b2 + b781162 commit 3ef6cce

37 files changed

+859
-119
lines changed

.sqlx/query-226259d2006f1a0bc904f5a25f9bbc71c19a867b626493b9f973a5cc09f5e3c0.json

+15
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.sqlx/query-30b117d8b863a0785fcb164bc428ea5591fe28ea9e07170906eb78c3ae4531d8.json

+14
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.sqlx/query-a1836fa232271fdd5d61f0d34048741a269f654ef6eb1fa2f6b95e68ed7e04e1.json

+28
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.sqlx/query-bcba1892b88c1aecf5fb4112c03b4ec9ffc2f61e38c74630dac5e32f3736c113.json

+26
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.sqlx/query-c0883840b92550c4a2ba682d15ee4841bf9cdce2d87fa952457b157afbe2d756.json

+22
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.lock

+4-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ sqlx = { version = "0.7", features = [
7373
"uuid",
7474
] }
7575
ssh-key = "0.6"
76-
struct-patch = "0.7"
76+
struct-patch = "0.8"
7777
tera = "1.20"
7878
thiserror = "1.0"
7979
# match axum-extra -> cookies
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DROP TABLE enterprisesettings;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
CREATE TABLE enterprisesettings (
2+
id bigserial PRIMARY KEY,
3+
admin_device_management BOOLEAN NOT NULL DEFAULT false
4+
);
5+
6+
INSERT INTO enterprisesettings (admin_device_management) values (false);

src/config.rs

-28
Original file line numberDiff line numberDiff line change
@@ -163,34 +163,6 @@ pub struct DefGuardConfig {
163163
#[serde(skip_serializing)]
164164
pub gateway_disconnection_notification_timeout: Duration,
165165

166-
#[arg(
167-
long,
168-
env = "DEFGUARD_LICENSE_SERVER_URL",
169-
default_value = "https://update-service-dev.teonite.net/api/license/refresh"
170-
)]
171-
#[serde(skip_serializing)]
172-
pub license_server_url: String,
173-
174-
#[arg(long, env = "DEFGUARD_LICENSE_CHECK_PERIOD", default_value = "10s")]
175-
#[serde(skip_serializing)]
176-
pub license_check_period: Duration,
177-
178-
#[arg(
179-
long,
180-
env = "DEFGUARD_LICENSE_CHECK_PERIOD_RENEWAL_WINDOW",
181-
default_value = "5s"
182-
)]
183-
#[serde(skip_serializing)]
184-
pub license_check_period_renewal_window: Duration,
185-
186-
#[arg(
187-
long,
188-
env = "DEFGUARD_LICENSE_CHECK_PERIOD_NO_LICENSE",
189-
default_value = "15s"
190-
)]
191-
#[serde(skip_serializing)]
192-
pub license_check_period_no_license: Duration,
193-
194166
#[command(subcommand)]
195167
#[serde(skip_serializing)]
196168
pub cmd: Option<Command>,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
use model_derive::Model;
2+
use sqlx::PgExecutor;
3+
use struct_patch::Patch;
4+
5+
use crate::enterprise::license::{get_cached_license, validate_license};
6+
7+
#[derive(Model, Deserialize, Serialize, Patch)]
8+
#[patch(attribute(derive(Serialize, Deserialize)))]
9+
pub struct EnterpriseSettings {
10+
#[serde(skip)]
11+
pub id: Option<i64>,
12+
// If true, only admins can manage devices
13+
pub admin_device_management: bool,
14+
}
15+
16+
// We want to be conscious of what the defaults are here
17+
#[allow(clippy::derivable_impls)]
18+
impl Default for EnterpriseSettings {
19+
fn default() -> Self {
20+
Self {
21+
id: None,
22+
admin_device_management: false,
23+
}
24+
}
25+
}
26+
27+
impl EnterpriseSettings {
28+
/// If license is valid returns current [`EnterpriseSettings`] object.
29+
/// Otherwise returns [`EnterpriseSettings::default()`].
30+
pub async fn get<'e, E>(executor: E) -> Result<Self, sqlx::Error>
31+
where
32+
E: PgExecutor<'e>,
33+
{
34+
// avoid holding the rwlock across await, makes the future !Send
35+
// and therefore unusable in axum handlers
36+
let is_valid = {
37+
let license = get_cached_license();
38+
validate_license(license.as_ref()).is_ok()
39+
};
40+
if is_valid {
41+
let settings = Self::find_by_id(executor, 1).await?;
42+
Ok(settings.expect("EnterpriseSettings not found"))
43+
} else {
44+
Ok(EnterpriseSettings::default())
45+
}
46+
}
47+
}

src/enterprise/db/models/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
pub mod enterprise_settings;
12
pub mod openid_provider;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
use axum::{extract::State, http::StatusCode, Json};
2+
use serde_json::json;
3+
use struct_patch::Patch;
4+
5+
use super::LicenseInfo;
6+
use crate::{
7+
appstate::AppState,
8+
auth::{AdminRole, SessionInfo},
9+
enterprise::db::models::enterprise_settings::{EnterpriseSettings, EnterpriseSettingsPatch},
10+
handlers::{ApiResponse, ApiResult},
11+
};
12+
13+
pub async fn get_enterprise_settings(
14+
session: SessionInfo,
15+
State(appstate): State<AppState>,
16+
) -> ApiResult {
17+
debug!(
18+
"User {} retrieving enterprise settings",
19+
session.user.username
20+
);
21+
let settings = EnterpriseSettings::get(&appstate.pool).await?;
22+
info!(
23+
"User {} retrieved enterprise settings",
24+
session.user.username
25+
);
26+
Ok(ApiResponse {
27+
json: json!(settings),
28+
status: StatusCode::OK,
29+
})
30+
}
31+
32+
pub async fn patch_enterprise_settings(
33+
_license: LicenseInfo,
34+
_admin: AdminRole,
35+
State(appstate): State<AppState>,
36+
session: SessionInfo,
37+
Json(data): Json<EnterpriseSettingsPatch>,
38+
) -> ApiResult {
39+
debug!(
40+
"Admin {} patching enterprise settings.",
41+
session.user.username,
42+
);
43+
let mut settings = EnterpriseSettings::get(&appstate.pool).await?;
44+
45+
settings.apply(data);
46+
settings.save(&appstate.pool).await?;
47+
info!("Admin {} patched settings.", session.user.username);
48+
Ok(ApiResponse::default())
49+
}

src/enterprise/handlers/mod.rs

+32-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
use crate::{
2+
auth::SessionInfo,
23
enterprise::license::validate_license,
34
handlers::{ApiResponse, ApiResult},
45
};
56

7+
pub mod enterprise_settings;
68
pub mod openid_login;
79
pub mod openid_providers;
810

@@ -12,13 +14,16 @@ use axum::{
1214
http::{request::Parts, StatusCode},
1315
};
1416

15-
use super::license::get_cached_license;
17+
use super::{db::models::enterprise_settings::EnterpriseSettings, license::get_cached_license};
1618
use crate::{appstate::AppState, error::WebError};
1719

1820
pub struct LicenseInfo {
1921
pub valid: bool,
2022
}
2123

24+
/// Used to check if user is allowed to manage his devices.
25+
pub struct CanManageDevices;
26+
2227
#[async_trait]
2328
impl<S> FromRequestParts<S> for LicenseInfo
2429
where
@@ -30,7 +35,7 @@ where
3035
async fn from_request_parts(_parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
3136
let license = get_cached_license();
3237

33-
match validate_license((*license).as_ref()) {
38+
match validate_license(license.as_ref()) {
3439
// Useless struct, but may come in handy later
3540
Ok(_) => Ok(LicenseInfo { valid: true }),
3641
Err(e) => Err(WebError::Forbidden(e.to_string())),
@@ -41,10 +46,34 @@ where
4146
pub async fn check_enterprise_status() -> ApiResult {
4247
let license = get_cached_license();
4348

44-
let valid = validate_license((*license).as_ref()).is_ok();
49+
let valid = validate_license((license).as_ref()).is_ok();
4550

4651
Ok(ApiResponse {
4752
json: serde_json::json!({ "enabled": valid }),
4853
status: StatusCode::OK,
4954
})
5055
}
56+
57+
#[async_trait]
58+
impl<S> FromRequestParts<S> for CanManageDevices
59+
where
60+
S: Send + Sync,
61+
AppState: FromRef<S>,
62+
{
63+
type Rejection = WebError;
64+
65+
/// Returns an error if current session user is not allowed to manage devices.
66+
/// The permission is defined by [`EnterpriseSettings::admin_device_management`] setting.
67+
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
68+
let appstate = AppState::from_ref(state);
69+
let session = SessionInfo::from_request_parts(parts, state).await?;
70+
let settings = EnterpriseSettings::get(&appstate.pool).await?;
71+
if settings.admin_device_management && !session.is_admin {
72+
Err(WebError::Forbidden(
73+
"Only admin users can manage devices".into(),
74+
))
75+
} else {
76+
Ok(Self)
77+
}
78+
}
79+
}

0 commit comments

Comments
 (0)