Skip to content

Commit

Permalink
Add new endpoint to get ssh keys from other user (#277)
Browse files Browse the repository at this point in the history
* add endpoint to retrieve other user ssh keys

* update ssh_key regex

* Remove forgotten unwrap

* change to ranked route

* Add test

* respond with correct content-type matching accept header
  • Loading branch information
xerbalind authored Jul 28, 2024
1 parent 9c2ad8c commit 5187cc2
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 15 deletions.
58 changes: 44 additions & 14 deletions src/controllers/users_controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ pub async fn show_user<'r>(
html: template!("users/show.html";
user: User = user.clone(),
current_user: User = session.user,
errors: Option<ValidationErrors> = None
),
json: Json(user),
})
Expand All @@ -58,6 +59,27 @@ pub async fn show_user<'r>(
}
}

#[get("/users/<username>/keys", rank = 1)]
pub async fn show_ssh_key<'r>(
db: DbConn,
username: String,
) -> Result<impl Responder<'r, 'static>> {
let user = User::find_by_username(username, &db).await?;
let mut keys = vec![];
if let Some(ssh_keys) = user.ssh_key {
for line in ssh_keys.lines() {
let line = line.trim();
if !line.is_empty() {
keys.push(line.to_string())
}
}
}
Ok(Accepter {
html: template!("users/keys.html"; keys: String = keys.join("\n")),
json: Json(keys),
})
}

#[get("/users")]
pub async fn list_users<'r>(
session: AdminSession,
Expand Down Expand Up @@ -184,27 +206,35 @@ pub async fn register<'r>(
}

#[put("/users/<username>", data = "<change>")]
pub async fn update_user<'r>(
pub async fn update_user<'r, 'o: 'r>(
username: String,
change: Api<UserChange>,
session: UserSession,
db: DbConn,
) -> Result<
Either<
impl Responder<'r, 'static>,
Custom<impl Debug + Responder<'r, 'static>>,
>,
> {
) -> Result<impl Responder<'r, 'o>> {
let mut user = User::find_by_username(username, &db).await?;
if session.user.id == user.id || session.user.admin {
user.change_with(change.into_inner())?;
let user = user.update(&db).await?;
Ok(Left(Accepter {
html: Redirect::to(uri!(show_user(user.username))),
json: Custom(Status::NoContent, ()),
}))
match user.change_with(change.into_inner()) {
Ok(()) => {
let user = user.update(&db).await?;
Ok(OneOf::One(Accepter {
html: Redirect::to(uri!(show_user(user.username))),
json: Custom(Status::NoContent, ()),
}))
},
Err(ZauthError::ValidationError(errors)) => Ok(OneOf::Two(Custom(
Status::UnprocessableEntity,
template! {
"users/show.html";
user: User = user,
current_user: User = session.user,
errors: Option<ValidationErrors> = Some(errors.clone()),
},
))),
Err(other) => Err(other),
}
} else {
Ok(Right(Custom(Status::Forbidden, ())))
Ok(OneOf::Three(Custom(Status::Forbidden, ())))
}
}

Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ fn assemble(rocket: Rocket<Build>) -> Rocket<Build> {
users_controller::register,
users_controller::current_user,
users_controller::show_user,
users_controller::show_ssh_key,
users_controller::list_users,
users_controller::update_user,
users_controller::change_state,
Expand Down
12 changes: 11 additions & 1 deletion src/models/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,7 @@ impl User {
self.ssh_key = Some(ssh_key);
}
self.subscribed_to_mailing_list = change.subscribed_to_mailing_list;
self.validate()?;
Ok(())
}

Expand Down Expand Up @@ -576,7 +577,16 @@ fn validate_ssh_key_list(
) -> std::result::Result<(), ValidationError> {
lazy_static! {
static ref SSH_KEY_REGEX: Regex = Regex::new(
r"ssh-(rsa|dsa|ecdsa|ed25519) [a-zA-Z0-9+/]{1,750}={0,3}( [^ ]+)?"
r"(?x)^
(
ssh-(rsa|dss|ecdsa|ed25519)|
ecdsa-sha2-nistp(256|384|521)|
sk-(
[email protected]|
[email protected]
)
)
\s[a-zA-Z0-9+/]{1,750}={0,3}( \S+)?"
)
.unwrap();
}
Expand Down
1 change: 1 addition & 0 deletions templates/users/keys.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<pre>{{keys}}</pre>
9 changes: 9 additions & 0 deletions templates/users/show.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@

<div class="title">Update profile information</div>

<!-- Error messages -->
{% match errors %}
{% when Some with (errors) %}
<div class="notification is-danger is-light">
{{ errors }}
</div>
{% when None %}
{% endmatch %}

<form class="profile-edit-form" action="/users/{{ user.username }}" method="POST">
<input type="hidden" name="_method" value="put"/>

Expand Down
55 changes: 55 additions & 0 deletions tests/users.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,20 @@ async fn show_user_as_admin() {
#[rocket::async_test]
async fn update_self() {
common::as_user(async move |http_client: HttpClient, db, user: User| {
let response = http_client
.put(format!("/users/{}", user.username))
.header(ContentType::Form)
.header(Accept::JSON)
.body("ssh_key=ssh-fake%20supersecretkey")
.dispatch()
.await;

assert_eq!(
response.status(),
Status::UnprocessableEntity,
"user should not be able to update invalid public ssh key"
);

let response = http_client
.put(format!("/users/{}", user.username))
.header(ContentType::Form)
Expand Down Expand Up @@ -1000,3 +1014,44 @@ async fn validate_unique_username() {
})
.await;
}

#[rocket::async_test]
async fn get_keys() {
common::as_visitor(async move |http_client: HttpClient, db| {
let mut user = User::create(
NewUser {
username: String::from("user"),
password: String::from("password"),
full_name: String::from("name"),
email: String::from("[email protected]"),
ssh_key: None,
not_a_robot: true,
},
common::BCRYPT_COST,
&db,
)
.await
.unwrap();

// User::create throws away ssh_key in NewUser.
user.ssh_key = Some(String::from("ssh-rsa rsa \n ssh-ed25519 ed25519"));
let user = user.update(&db).await.unwrap();

let response = http_client
.get(format!("/users/{}/keys", user.username))
.header(Accept::JSON)
.dispatch()
.await;

assert_eq!(response.status(), Status::Ok);

let keys = response
.into_json::<Vec<String>>()
.await
.expect("response json");
assert_eq!(keys.len(), 2);
assert_eq!(keys[0], "ssh-rsa rsa");
assert_eq!(keys[1], "ssh-ed25519 ed25519");
})
.await;
}

0 comments on commit 5187cc2

Please sign in to comment.