Skip to content

Commit

Permalink
Add updateTrustedDomain GraphQL API
Browse files Browse the repository at this point in the history
Closes #2
  • Loading branch information
henry0715-dev committed Aug 27, 2024
1 parent 69e2db0 commit 63f58fa
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Versioning](https://semver.org/spec/v2.0.0.html).
during sign-in.
- Added ip access control based on the `allow_access_from` field of `Account`
during sign-in.
- Added the `updateTrustedDomain` GraphQL API to modify the list of trusted domains.

### Changed

Expand Down
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ num-traits = "0.2"
reqwest = { version = "0.12", default-features = false, features = [
"rustls-tls-native-roots",
] }
review-database = { git = "https://github.com/petabi/review-database.git", rev = "66ccdd49" }
review-database = { git = "https://github.com/petabi/review-database.git", rev = "bc20b74" } #TODO Need to modify the version.
review-protocol = { git = "https://github.com/petabi/review-protocol.git", rev = "4a6ea44" }
roxy = { git = "https://github.com/aicers/roxy.git", tag = "0.2.1" }
rustls = "0.23" #should be the same version as what reqwest depends on
Expand All @@ -41,6 +41,7 @@ tokio = "1"
tower-http = { version = "0.5", features = ["fs", "trace"] }
tracing = "0.1"
vinum = { git = "https://github.com/vinesystems/vinum.git", tag = "1.0.3" }
regex = "1.10.6"

[dev-dependencies]
assert-json-diff = "2.0.2"
Expand Down
5 changes: 4 additions & 1 deletion src/graphql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,9 @@ struct MockAgentManager {}
#[cfg(test)]
#[async_trait::async_trait]
impl AgentManager for MockAgentManager {
async fn broadcast_trusted_domains(&self) -> Result<(), anyhow::Error> {
Ok(())
}
async fn broadcast_internal_networks(
&self,
_networks: &[u8],
Expand Down Expand Up @@ -671,7 +674,7 @@ mod tests {
#[tokio::test]
async fn unimplemented_agent_manager() {
let agent_manager = super::MockAgentManager {};
assert!(agent_manager.broadcast_trusted_domains().await.is_err());
assert!(agent_manager.broadcast_trusted_domains().await.is_ok());
assert!(agent_manager
.broadcast_trusted_user_agent_list(&[])
.await
Expand Down
142 changes: 140 additions & 2 deletions src/graphql/trusted_domain.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use async_graphql::{
connection::{query, Connection, EmptyFields},
Context, Object, Result, SimpleObject,
Context, InputObject, Object, Result, SimpleObject,
};
use regex::Regex;

use super::{AgentManager, BoxedAgentManager, Role, RoleGuard};

Expand Down Expand Up @@ -61,6 +62,33 @@ impl TrustedDomainMutation {
Ok(name)
}

/// Update a trusted domain, returning the new value if it passes domain validation.
#[graphql(guard = "RoleGuard::new(Role::SystemAdministrator)
.or(RoleGuard::new(Role::SecurityAdministrator))")]
async fn update_trusted_domain(
&self,
ctx: &Context<'_>,
old: TrustedDomainInput,
new: TrustedDomainInput,
) -> Result<String> {
if !is_valid_domain(&new.name) {
return Err(TrustedDomainError::InvalidDomainName(String::from(&new.name)).into());
}

let name = {
let store = crate::graphql::get_store(ctx).await?;
let map = store.trusted_domain_map();
let old = review_database::TrustedDomain::from(old);
let new = review_database::TrustedDomain::from(new);
map.update(&old, &new)?;
new.name
};

let agent_manager = ctx.data::<BoxedAgentManager>()?;
agent_manager.broadcast_trusted_domains().await?;
Ok(name)
}

/// Removes a trusted domain, returning the old value if it existed.
#[graphql(
guard = "RoleGuard::new(Role::SystemAdministrator).or(RoleGuard::new(Role::SecurityAdministrator))"
Expand Down Expand Up @@ -93,6 +121,21 @@ impl From<review_database::TrustedDomain> for TrustedDomain {
}
}

#[derive(InputObject)]
pub(super) struct TrustedDomainInput {
name: String,
remarks: String,
}

impl From<TrustedDomainInput> for review_database::TrustedDomain {
fn from(input: TrustedDomainInput) -> Self {
Self {
name: input.name,
remarks: input.remarks,
}
}
}

async fn load(
ctx: &Context<'_>,
after: Option<String>,
Expand All @@ -105,9 +148,25 @@ async fn load(
super::load_edges(&map, after, before, first, last, EmptyFields)
}

fn is_valid_domain(domain: &str) -> bool {
let domain_regex =
Regex::new(r"^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$").unwrap();
domain_regex.is_match(domain)
}

#[derive(Debug, thiserror::Error)]
#[allow(clippy::module_name_repetitions)]
pub enum TrustedDomainError {
#[error("Invalid domain name: {0}")]
InvalidDomainName(String),
}

#[cfg(test)]
mod tests {
use crate::graphql::TestSchema;
use std::net::SocketAddr;

use crate::graphql::trusted_domain::is_valid_domain;
use crate::graphql::{BoxedAgentManager, MockAgentManager, TestSchema};

#[tokio::test]
async fn trusted_domain_list() {
Expand All @@ -117,4 +176,83 @@ mod tests {
.await;
assert_eq!(res.data.to_string(), r#"{trustedDomainList: {edges: []}}"#);
}

#[tokio::test]
async fn update_trusted_domain() {
let agent_manager: BoxedAgentManager = Box::new(MockAgentManager {});
let test_addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
let schema = TestSchema::new_with(agent_manager, Some(test_addr)).await;
let insert_query = r#"
mutation {
insertTrustedDomain(
name: "test.com"
remarks: "origin_remarks"
)
}
"#;
let update_query = r#"
mutation {
updateTrustedDomain(
old: {
name: "test.com"
remarks: "origin_remarks"
}
new: {
name: "test2.com"
remarks: "updated_remarks"
}
)
}
"#;

let update_error_query = r#"
mutation {
updateTrustedDomain(
old: {
name: "test2.com"
remarks: "origin_remarks"
}
new: {
name: "test"
remarks: "updated_remarks"
}
)
}
"#;
let res = schema.execute(update_query).await;
assert_eq!(
res.errors.first().unwrap().message,
"no such entry".to_string()
);

let res = schema.execute(insert_query).await;
assert_eq!(res.data.to_string(), r#"{insertTrustedDomain: "test.com"}"#);

let res = schema.execute(update_query).await;
assert_eq!(
res.data.to_string(),
r#"{updateTrustedDomain: "test2.com"}"#
);

let res = schema.execute(update_error_query).await;
assert_eq!(
res.errors.first().unwrap().message,
"Invalid domain name: test".to_string()
);
}

#[test]
fn valid_domain() {
let test_domains = vec![
"ex.com",
"test.domain.co.kr",
"test.or.org",
"test-1.sample",
"error",
];

let res: Vec<_> = test_domains.iter().map(|&x| is_valid_domain(x)).collect();
let expect = vec![true, true, true, true, false];
assert_eq!(res, expect);
}
}

0 comments on commit 63f58fa

Please sign in to comment.