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 Nov 27, 2024
1 parent fee9a08 commit 15add15
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 2 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ file is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and
this project adheres to [Semantic
Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- Added the `updateTrustedDomain` GraphQL API to modify the list of trusted
domains.

## [0.24.0] - 2024-11-19

### Added
Expand Down Expand Up @@ -732,6 +739,7 @@ Versioning](https://semver.org/spec/v2.0.0.html).

- An initial version.

[Unreleased]: https://github.com/aicers/review-web/compare/0.24.0...main
[0.24.0]: https://github.com/aicers/review-web/compare/0.23.0...0.24.0
[0.23.0]: https://github.com/aicers/review-web/compare/0.22.0...0.23.0
[0.22.0]: https://github.com/aicers/review-web/compare/0.21.0...0.22.0
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ tokio = "1"
tower-http = { version = "0.6", features = ["fs", "trace"] }
tracing = "0.1"
vinum = { git = "https://github.com/vinesystems/vinum.git", tag = "1.0.3" }
regex = "1.11.1"

[dev-dependencies]
assert-json-diff = "2"
Expand Down
1 change: 1 addition & 0 deletions src/graphql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,7 @@ mod tests {
#[tokio::test]
async fn unimplemented_agent_manager() {
let agent_manager = super::MockAgentManager {};
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::{Connection, EmptyFields},
Context, Object, Result, SimpleObject,
Context, InputObject, Object, Result, SimpleObject,
};
use regex::Regex;

use super::{AgentManager, BoxedAgentManager, Role, RoleGuard};
use crate::graphql::query_with_constraints;
Expand Down Expand Up @@ -62,6 +63,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 @@ -94,6 +122,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 @@ -106,9 +149,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 Down Expand Up @@ -157,4 +216,83 @@ mod tests {
r#"{trustedDomainList: {edges: [{node: {name: "example2.org"}}]}}"#
);
}

#[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 15add15

Please sign in to comment.