This commit is contained in:
		@@ -104,10 +104,10 @@ impl Token {
 | 
			
		||||
 | 
			
		||||
    /// Check whether a token is expired or not
 | 
			
		||||
    pub fn is_expired(&self) -> bool {
 | 
			
		||||
        if let Some(max_inactivity) = self.max_inactivity {
 | 
			
		||||
            if max_inactivity + self.last_used < time() {
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
        if let Some(max_inactivity) = self.max_inactivity
 | 
			
		||||
            && max_inactivity + self.last_used < time()
 | 
			
		||||
        {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        false
 | 
			
		||||
@@ -188,10 +188,10 @@ impl NewToken {
 | 
			
		||||
            return Some(err);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if let Some(t) = self.max_inactivity {
 | 
			
		||||
            if t < 3600 {
 | 
			
		||||
                return Some("API tokens shall be valid for at least 1 hour!");
 | 
			
		||||
            }
 | 
			
		||||
        if let Some(t) = self.max_inactivity
 | 
			
		||||
            && t < 3600
 | 
			
		||||
        {
 | 
			
		||||
            return Some("API tokens shall be valid for at least 1 hour!");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        None
 | 
			
		||||
 
 | 
			
		||||
@@ -31,12 +31,12 @@ pub async fn upload(MultipartForm(mut form): MultipartForm<UploadDiskImageForm>)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Check file mime type
 | 
			
		||||
    if let Some(mime_type) = file.content_type {
 | 
			
		||||
        if !constants::ALLOWED_DISK_IMAGES_MIME_TYPES.contains(&mime_type.as_ref()) {
 | 
			
		||||
            return Ok(HttpResponse::BadRequest().json(format!(
 | 
			
		||||
                "Unsupported file type for disk upload: {mime_type}"
 | 
			
		||||
            )));
 | 
			
		||||
        }
 | 
			
		||||
    if let Some(mime_type) = file.content_type
 | 
			
		||||
        && !constants::ALLOWED_DISK_IMAGES_MIME_TYPES.contains(&mime_type.as_ref())
 | 
			
		||||
    {
 | 
			
		||||
        return Ok(HttpResponse::BadRequest().json(format!(
 | 
			
		||||
            "Unsupported file type for disk upload: {mime_type}"
 | 
			
		||||
        )));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Extract and check file name
 | 
			
		||||
 
 | 
			
		||||
@@ -31,11 +31,11 @@ pub async fn upload_file(MultipartForm(mut form): MultipartForm<UploadIsoForm>)
 | 
			
		||||
        return Ok(HttpResponse::BadRequest().json("File is too large!"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if let Some(m) = &file.content_type {
 | 
			
		||||
        if !constants::ALLOWED_ISO_MIME_TYPES.contains(&m.to_string().as_str()) {
 | 
			
		||||
            log::error!("Uploaded ISO file has an invalid mimetype!");
 | 
			
		||||
            return Ok(HttpResponse::BadRequest().json("Invalid mimetype!"));
 | 
			
		||||
        }
 | 
			
		||||
    if let Some(m) = &file.content_type
 | 
			
		||||
        && !constants::ALLOWED_ISO_MIME_TYPES.contains(&m.to_string().as_str())
 | 
			
		||||
    {
 | 
			
		||||
        log::error!("Uploaded ISO file has an invalid mimetype!");
 | 
			
		||||
        return Ok(HttpResponse::BadRequest().json("Invalid mimetype!"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let file_name = match &file.file_name {
 | 
			
		||||
@@ -87,16 +87,16 @@ pub async fn upload_from_url(req: web::Json<DownloadFromURLReq>) -> HttpResult {
 | 
			
		||||
 | 
			
		||||
    let response = reqwest::get(&req.url).await?;
 | 
			
		||||
 | 
			
		||||
    if let Some(len) = response.content_length() {
 | 
			
		||||
        if len > constants::ISO_MAX_SIZE.as_bytes() as u64 {
 | 
			
		||||
            return Ok(HttpResponse::BadRequest().json("File is too large!"));
 | 
			
		||||
        }
 | 
			
		||||
    if let Some(len) = response.content_length()
 | 
			
		||||
        && len > constants::ISO_MAX_SIZE.as_bytes() as u64
 | 
			
		||||
    {
 | 
			
		||||
        return Ok(HttpResponse::BadRequest().json("File is too large!"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if let Some(ct) = response.headers().get("content-type") {
 | 
			
		||||
        if !constants::ALLOWED_ISO_MIME_TYPES.contains(&ct.to_str()?) {
 | 
			
		||||
            return Ok(HttpResponse::BadRequest().json("Invalid file mimetype!"));
 | 
			
		||||
        }
 | 
			
		||||
    if let Some(ct) = response.headers().get("content-type")
 | 
			
		||||
        && !constants::ALLOWED_ISO_MIME_TYPES.contains(&ct.to_str()?)
 | 
			
		||||
    {
 | 
			
		||||
        return Ok(HttpResponse::BadRequest().json("Invalid file mimetype!"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let mut stream = response.bytes_stream();
 | 
			
		||||
 
 | 
			
		||||
@@ -128,21 +128,21 @@ impl FromRequest for ApiAuthExtractor {
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if let Some(ip) = token.ip_restriction {
 | 
			
		||||
                if !ip.contains(remote_ip.0) {
 | 
			
		||||
                    log::error!(
 | 
			
		||||
                        "Attempt to use a token for an unauthorized IP! {remote_ip:?} token_id={}",
 | 
			
		||||
                        token.id.0
 | 
			
		||||
                    );
 | 
			
		||||
                    return Err(ErrorUnauthorized("Token cannot be used from this IP!"));
 | 
			
		||||
                }
 | 
			
		||||
            if let Some(ip) = token.ip_restriction
 | 
			
		||||
                && !ip.contains(remote_ip.0)
 | 
			
		||||
            {
 | 
			
		||||
                log::error!(
 | 
			
		||||
                    "Attempt to use a token for an unauthorized IP! {remote_ip:?} token_id={}",
 | 
			
		||||
                    token.id.0
 | 
			
		||||
                );
 | 
			
		||||
                return Err(ErrorUnauthorized("Token cannot be used from this IP!"));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if token.should_update_last_activity() {
 | 
			
		||||
                if let Err(e) = api_tokens::refresh_last_used(token.id).await {
 | 
			
		||||
                    log::error!("Could not update token last activity! {e}");
 | 
			
		||||
                    return Err(ErrorBadRequest("Couldn't refresh token last activity!"));
 | 
			
		||||
                }
 | 
			
		||||
            if token.should_update_last_activity()
 | 
			
		||||
                && let Err(e) = api_tokens::refresh_last_used(token.id).await
 | 
			
		||||
            {
 | 
			
		||||
                log::error!("Could not update token last activity! {e}");
 | 
			
		||||
                return Err(ErrorBadRequest("Couldn't refresh token last activity!"));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Ok(ApiAuthExtractor { token, claims })
 | 
			
		||||
 
 | 
			
		||||
@@ -96,28 +96,28 @@ impl NetworkInfo {
 | 
			
		||||
            return Err(StructureExtraction("network name is invalid!").into());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if let Some(n) = &self.title {
 | 
			
		||||
            if n.contains('\n') {
 | 
			
		||||
                return Err(StructureExtraction("Network title contain newline char!").into());
 | 
			
		||||
            }
 | 
			
		||||
        if let Some(n) = &self.title
 | 
			
		||||
            && n.contains('\n')
 | 
			
		||||
        {
 | 
			
		||||
            return Err(StructureExtraction("Network title contain newline char!").into());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if let Some(dev) = &self.device {
 | 
			
		||||
            if !regex!("^[a-zA-Z0-9]+$").is_match(dev) {
 | 
			
		||||
                return Err(StructureExtraction("Network device name is invalid!").into());
 | 
			
		||||
            }
 | 
			
		||||
        if let Some(dev) = &self.device
 | 
			
		||||
            && !regex!("^[a-zA-Z0-9]+$").is_match(dev)
 | 
			
		||||
        {
 | 
			
		||||
            return Err(StructureExtraction("Network device name is invalid!").into());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if let Some(bridge) = &self.bridge_name {
 | 
			
		||||
            if !regex!("^[a-zA-Z0-9]+$").is_match(bridge) {
 | 
			
		||||
                return Err(StructureExtraction("Network bridge name is invalid!").into());
 | 
			
		||||
            }
 | 
			
		||||
        if let Some(bridge) = &self.bridge_name
 | 
			
		||||
            && !regex!("^[a-zA-Z0-9]+$").is_match(bridge)
 | 
			
		||||
        {
 | 
			
		||||
            return Err(StructureExtraction("Network bridge name is invalid!").into());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if let Some(domain) = &self.domain {
 | 
			
		||||
            if !regex!("^[a-zA-Z0-9.]+$").is_match(domain) {
 | 
			
		||||
                return Err(StructureExtraction("Domain name is invalid!").into());
 | 
			
		||||
            }
 | 
			
		||||
        if let Some(domain) = &self.domain
 | 
			
		||||
            && !regex!("^[a-zA-Z0-9.]+$").is_match(domain)
 | 
			
		||||
        {
 | 
			
		||||
            return Err(StructureExtraction("Domain name is invalid!").into());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let mut ips = Vec::with_capacity(2);
 | 
			
		||||
@@ -303,16 +303,16 @@ impl NetworkInfo {
 | 
			
		||||
 | 
			
		||||
    /// Check if at least one NAT definition was specified on this interface
 | 
			
		||||
    pub fn has_nat_def(&self) -> bool {
 | 
			
		||||
        if let Some(ipv4) = &self.ip_v4 {
 | 
			
		||||
            if ipv4.nat.is_some() {
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
        if let Some(ipv4) = &self.ip_v4
 | 
			
		||||
            && ipv4.nat.is_some()
 | 
			
		||||
        {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if let Some(ipv6) = &self.ip_v6 {
 | 
			
		||||
            if ipv6.nat.is_some() {
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
        if let Some(ipv6) = &self.ip_v6
 | 
			
		||||
            && ipv6.nat.is_some()
 | 
			
		||||
        {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        false
 | 
			
		||||
 
 | 
			
		||||
@@ -43,14 +43,12 @@ impl From<&String> for NetworkFilterMacAddressOrVar {
 | 
			
		||||
fn extract_mac_address_or_var(
 | 
			
		||||
    n: &Option<NetworkFilterMacAddressOrVar>,
 | 
			
		||||
) -> anyhow::Result<Option<String>> {
 | 
			
		||||
    if let Some(mac) = n {
 | 
			
		||||
        if !mac.is_valid() {
 | 
			
		||||
            return Err(NetworkFilterExtraction(format!(
 | 
			
		||||
                "Invalid mac address or variable! {}",
 | 
			
		||||
                mac.0
 | 
			
		||||
            ))
 | 
			
		||||
            .into());
 | 
			
		||||
        }
 | 
			
		||||
    if let Some(mac) = n
 | 
			
		||||
        && !mac.is_valid()
 | 
			
		||||
    {
 | 
			
		||||
        return Err(
 | 
			
		||||
            NetworkFilterExtraction(format!("Invalid mac address or variable! {}", mac.0)).into(),
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(n.as_ref().map(|n| n.0.to_string()))
 | 
			
		||||
@@ -83,34 +81,34 @@ impl<const V: usize> From<&String> for NetworkFilterIPOrVar<V> {
 | 
			
		||||
fn extract_ip_or_var<const V: usize>(
 | 
			
		||||
    n: &Option<NetworkFilterIPOrVar<V>>,
 | 
			
		||||
) -> anyhow::Result<Option<String>> {
 | 
			
		||||
    if let Some(ip) = n {
 | 
			
		||||
        if !ip.is_valid() {
 | 
			
		||||
            return Err(NetworkFilterExtraction(format!(
 | 
			
		||||
                "Invalid IPv{V} address or variable! {}",
 | 
			
		||||
                ip.0
 | 
			
		||||
            ))
 | 
			
		||||
            .into());
 | 
			
		||||
        }
 | 
			
		||||
    if let Some(ip) = n
 | 
			
		||||
        && !ip.is_valid()
 | 
			
		||||
    {
 | 
			
		||||
        return Err(NetworkFilterExtraction(format!(
 | 
			
		||||
            "Invalid IPv{V} address or variable! {}",
 | 
			
		||||
            ip.0
 | 
			
		||||
        ))
 | 
			
		||||
        .into());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(n.as_ref().map(|n| n.0.to_string()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn extract_ip_mask<const V: usize>(n: Option<u8>) -> anyhow::Result<Option<u8>> {
 | 
			
		||||
    if let Some(mask) = n {
 | 
			
		||||
        if !net_utils::is_mask_valid(V, mask) {
 | 
			
		||||
            return Err(NetworkFilterExtraction(format!("Invalid IPv{V} mask! {mask}")).into());
 | 
			
		||||
        }
 | 
			
		||||
    if let Some(mask) = n
 | 
			
		||||
        && !net_utils::is_mask_valid(V, mask)
 | 
			
		||||
    {
 | 
			
		||||
        return Err(NetworkFilterExtraction(format!("Invalid IPv{V} mask! {mask}")).into());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(n)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn extract_nw_filter_comment(n: &Option<String>) -> anyhow::Result<Option<String>> {
 | 
			
		||||
    if let Some(comment) = n {
 | 
			
		||||
        if comment.len() > 256 || comment.contains('\"') || comment.contains('\n') {
 | 
			
		||||
            return Err(NetworkFilterExtraction(format!("Invalid comment! {comment}")).into());
 | 
			
		||||
        }
 | 
			
		||||
    if let Some(comment) = n
 | 
			
		||||
        && (comment.len() > 256 || comment.contains('\"') || comment.contains('\n'))
 | 
			
		||||
    {
 | 
			
		||||
        return Err(NetworkFilterExtraction(format!("Invalid comment! {comment}")).into());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(n.clone())
 | 
			
		||||
@@ -869,12 +867,10 @@ impl NetworkFilter {
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if let Some(priority) = self.priority {
 | 
			
		||||
            if !(-1000..=1000).contains(&priority) {
 | 
			
		||||
                return Err(
 | 
			
		||||
                    NetworkFilterExtraction("Network priority is invalid!".to_string()).into(),
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
        if let Some(priority) = self.priority
 | 
			
		||||
            && !(-1000..=1000).contains(&priority)
 | 
			
		||||
        {
 | 
			
		||||
            return Err(NetworkFilterExtraction("Network priority is invalid!".to_string()).into());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for fref in &self.join_filters {
 | 
			
		||||
 
 | 
			
		||||
@@ -118,22 +118,22 @@ impl VMInfo {
 | 
			
		||||
            XMLUuid::new_random()
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if let Some(n) = &self.genid {
 | 
			
		||||
            if !n.is_valid() {
 | 
			
		||||
                return Err(StructureExtraction("VM genid is invalid!").into());
 | 
			
		||||
            }
 | 
			
		||||
        if let Some(n) = &self.genid
 | 
			
		||||
            && !n.is_valid()
 | 
			
		||||
        {
 | 
			
		||||
            return Err(StructureExtraction("VM genid is invalid!").into());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if let Some(n) = &self.title {
 | 
			
		||||
            if n.contains('\n') {
 | 
			
		||||
                return Err(StructureExtraction("VM title contain newline char!").into());
 | 
			
		||||
            }
 | 
			
		||||
        if let Some(n) = &self.title
 | 
			
		||||
            && n.contains('\n')
 | 
			
		||||
        {
 | 
			
		||||
            return Err(StructureExtraction("VM title contain newline char!").into());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if let Some(group) = &self.group {
 | 
			
		||||
            if !regex!("^[a-zA-Z0-9]+$").is_match(&group.0) {
 | 
			
		||||
                return Err(StructureExtraction("VM group name is invalid!").into());
 | 
			
		||||
            }
 | 
			
		||||
        if let Some(group) = &self.group
 | 
			
		||||
            && !regex!("^[a-zA-Z0-9]+$").is_match(&group.0)
 | 
			
		||||
        {
 | 
			
		||||
            return Err(StructureExtraction("VM group name is invalid!").into());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if self.memory < constants::MIN_VM_MEMORY || self.memory > constants::MAX_VM_MEMORY {
 | 
			
		||||
 
 | 
			
		||||
@@ -60,10 +60,10 @@ pub struct Nat<IPv> {
 | 
			
		||||
 | 
			
		||||
impl<IPv> Nat<IPv> {
 | 
			
		||||
    pub fn check(&self) -> anyhow::Result<()> {
 | 
			
		||||
        if let NatSourceIP::Interface { name } = &self.host_ip {
 | 
			
		||||
            if !net_utils::is_net_interface_name_valid(name) {
 | 
			
		||||
                return Err(NatDefError::InvalidNatDef("Invalid nat interface name!").into());
 | 
			
		||||
            }
 | 
			
		||||
        if let NatSourceIP::Interface { name } = &self.host_ip
 | 
			
		||||
            && !net_utils::is_net_interface_name_valid(name)
 | 
			
		||||
        {
 | 
			
		||||
            return Err(NatDefError::InvalidNatDef("Invalid nat interface name!").into());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if let NatHostPort::Range { start, end } = &self.host_port {
 | 
			
		||||
@@ -84,10 +84,10 @@ impl<IPv> Nat<IPv> {
 | 
			
		||||
            return Err(NatDefError::InvalidNatDef("Invalid guest port!").into());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if let Some(comment) = &self.comment {
 | 
			
		||||
            if comment.len() > constants::NET_NAT_COMMENT_MAX_SIZE {
 | 
			
		||||
                return Err(NatDefError::InvalidNatDef("Comment is too large!").into());
 | 
			
		||||
            }
 | 
			
		||||
        if let Some(comment) = &self.comment
 | 
			
		||||
            && comment.len() > constants::NET_NAT_COMMENT_MAX_SIZE
 | 
			
		||||
        {
 | 
			
		||||
            return Err(NatDefError::InvalidNatDef("Comment is too large!").into());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user