Skip to content

Commit

Permalink
PREvants rejects request for app names with spaces and slashes in it
Browse files Browse the repository at this point in the history
  • Loading branch information
schrieveslaach committed Jul 10, 2019
1 parent ddc4d4e commit 0e3de1f
Show file tree
Hide file tree
Showing 8 changed files with 593 additions and 392 deletions.
815 changes: 434 additions & 381 deletions api/Cargo.lock

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ failure = "0.1"
futures = "0.1"
handlebars = "1.1"
hyper = "0.10"
lazy_static = "1.3"
log = "0.4"
multimap = "0.4"
serde = "1.0"
Expand Down Expand Up @@ -45,8 +46,8 @@ version = "0.12"
features = ["with_rocket"]

[dependencies.shiplift]
git = "https://github.com/schrieveslaach/shiplift.git"
rev = "5b775124537e78c1db307230c45c1f91e1d8353b"
git = "https://github.com/softprops/shiplift.git"
rev = "7d9718b33764dea4a85859e94337e471de091332"
default-features = false
features = ["unix-socket"]

Expand Down
14 changes: 9 additions & 5 deletions api/src/apps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@

use crate::models::request_info::RequestInfo;
use crate::models::service::{Service, ServiceConfig};
use crate::models::{AppName, AppNameError};
use crate::services::apps_service::AppsService;
use http_api_problem::HttpApiProblem;
use multimap::MultiMap;
use rocket::data::{self, FromDataSimple};
use rocket::http::RawStr;
use rocket::http::Status;
use rocket::request::{Form, Request};
use rocket::Outcome::{Failure, Success};
Expand All @@ -57,11 +57,13 @@ pub fn apps(

#[delete("/apps/<app_name>", format = "application/json")]
pub fn delete_app(
app_name: &RawStr,
app_name: Result<AppName, AppNameError>,
apps_service: State<AppsService>,
request_info: RequestInfo,
) -> Result<Json<Vec<Service>>, HttpApiProblem> {
let mut services = apps_service.delete_app(&app_name.to_string())?;
let app_name = app_name?;

let mut services = apps_service.delete_app(&app_name)?;

for service in services.iter_mut() {
service.set_base_url(request_info.get_base_url());
Expand All @@ -76,14 +78,16 @@ pub fn delete_app(
data = "<service_configs_data>"
)]
pub fn create_app(
app_name: &RawStr,
app_name: Result<AppName, AppNameError>,
apps_service: State<AppsService>,
create_app_form: Form<CreateAppOptions>,
request_info: RequestInfo,
service_configs_data: ServiceConfigsData,
) -> Result<Json<Vec<Service>>, HttpApiProblem> {
let app_name = app_name?;

let mut services = apps_service.create_or_update(
&app_name.to_string(),
&app_name,
create_app_form.replicate_from().clone(),
&service_configs_data.service_configs,
)?;
Expand Down
2 changes: 2 additions & 0 deletions api/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ extern crate clap;
#[macro_use]
extern crate failure;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate log;
#[macro_use]
extern crate rocket;
Expand Down
137 changes: 137 additions & 0 deletions api/src/models/app_name.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*-
* ========================LICENSE_START=================================
* PREvant REST API
* %%
* Copyright (C) 2018 - 2019 aixigo AG
* %%
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* =========================LICENSE_END==================================
*/

use http_api_problem::{HttpApiProblem, StatusCode};
use regex::Regex;
use rocket::http::RawStr;
use rocket::request::FromParam;
use std::collections::HashSet;
use std::ops::Deref;
use std::str::{FromStr, Utf8Error};

#[derive(Debug)]
pub struct AppName(String);

impl Deref for AppName {
type Target = String;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl FromStr for AppName {
type Err = AppNameError;

fn from_str(name: &str) -> Result<Self, Self::Err> {
lazy_static! {
static ref INVALID_CHARS_REGEX: Regex = Regex::new("(\\s|/)").unwrap();
}

match INVALID_CHARS_REGEX.captures(name) {
None => Ok(AppName(name.to_string())),
Some(captures) => {
let invalid_chars = captures
.iter()
.filter_map(|c| c)
.map(|c| c.as_str())
.collect::<HashSet<&str>>()
.into_iter()
.collect::<Vec<&str>>()
.join("");

return Err(AppNameError::InvalidChars { invalid_chars });
}
}
}
}

impl<'r> FromParam<'r> for AppName {
type Error = AppNameError;

fn from_param(param: &'r RawStr) -> Result<Self, Self::Error> {
AppName::from_str(&param.url_decode()?)
}
}

#[derive(Debug, Fail)]
pub enum AppNameError {
#[fail(
display = "Invalid characters in app name: “{}” are invalid.",
invalid_chars
)]
InvalidChars { invalid_chars: String },
#[fail(display = "Invalid url encoded parameter: {}", err)]
InvalidUrlDecodedParam { err: String },
}

impl From<Utf8Error> for AppNameError {
fn from(err: Utf8Error) -> Self {
AppNameError::InvalidUrlDecodedParam {
err: format!("{}", err),
}
}
}

impl From<AppNameError> for HttpApiProblem {
fn from(err: AppNameError) -> Self {
HttpApiProblem::with_title_from_status(StatusCode::BAD_REQUEST)
.set_detail(format!("{}", err))
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn should_create_app_name_from_str() {
let app_name = AppName::from_str("master").unwrap();

assert_eq!(app_name.0, "master");
}

#[test]
fn should_create_app_name_from_utf_str() {
let app_name = AppName::from_str("Üߥ$Ω").unwrap();

assert_eq!(app_name.0, "Üߥ$Ω");
}

#[test]
fn should_not_create_app_name_app_name_contains_whitespaces() {
let app_name = AppName::from_str(" master\n ");

assert!(app_name.is_err());
}

#[test]
fn should_not_create_app_name_app_name_contains_slashes() {
let app_name = AppName::from_str("feature/xxx");

assert!(app_name.is_err());
}
}
3 changes: 3 additions & 0 deletions api/src/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
* =========================LICENSE_END==================================
*/

pub use app_name::{AppName, AppNameError};

mod app_name;
pub mod request_info;
pub mod service;
pub mod ticket_info;
Expand Down
4 changes: 2 additions & 2 deletions api/src/services/service_templating.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ fn is_not_companion<'reg, 'rc>(
r: &'reg Handlebars,
ctx: &Context,
rc: &mut RenderContext<'reg>,
out: &mut Output,
out: &mut dyn Output,
) -> HelperResult {
let s = h
.param(0)
Expand Down Expand Up @@ -143,7 +143,7 @@ fn is_companion<'reg, 'rc>(
r: &'reg Handlebars,
ctx: &Context,
rc: &mut RenderContext<'reg>,
out: &mut Output,
out: &mut dyn Output,
) -> HelperResult {
let s = h
.param(0)
Expand Down
5 changes: 3 additions & 2 deletions api/src/webhooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,12 @@ use crate::apps::delete_app;
use crate::models::request_info::RequestInfo;
use crate::models::service::Service;
use crate::models::web_hook_info::WebHookInfo;
use crate::models::AppName;
use crate::services::apps_service::AppsService;
use http_api_problem::HttpApiProblem;
use rocket::http::RawStr;
use rocket::State;
use rocket_contrib::json::Json;
use std::str::FromStr;

#[post("/webhooks", format = "application/json", data = "<web_hook_info>")]
pub fn webhooks(
Expand All @@ -48,7 +49,7 @@ pub fn webhooks(
);

delete_app(
&RawStr::from_str(&web_hook_info.get_app_name()),
AppName::from_str(&web_hook_info.get_app_name()),
apps_service,
request_info,
)
Expand Down

0 comments on commit 0e3de1f

Please sign in to comment.