Skip to content

Require owner approval #1108

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Oct 6, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
45e9dd4
change insert into crate_owner table
natboehm Sep 25, 2017
071666d
i don't know if this works but i should commit
natboehm Sep 26, 2017
35d1f32
change owner_add to return a String message instead of boolean okay w…
natboehm Sep 27, 2017
64c894e
cargo fmt
natboehm Sep 27, 2017
c24906f
change message displayed from using id's to useful values
natboehm Sep 27, 2017
476dc03
consistant with capitalization
natboehm Sep 27, 2017
0f5b7da
add boolean back to struct to support old versions of cargo
natboehm Sep 28, 2017
c79243f
test functionality for addition of message field to struct returned b…
natboehm Sep 29, 2017
329cab3
update expected error message to match new error message
natboehm Oct 3, 2017
477933a
reorder statements at top of function
natboehm Oct 5, 2017
f6a9353
add debug statements
natboehm Oct 5, 2017
641baf0
change user login name to fix deadlock error between test_insert_into…
natboehm Oct 5, 2017
89d291c
delete owner test as it's pretty redundant, fix new_crate_owner test …
natboehm Oct 5, 2017
db114a2
fix test owners_can_remove_self such that a user is invited and invit…
natboehm Oct 5, 2017
0eaedf2
update error message to match new error message returned
natboehm Oct 5, 2017
04afe95
ran cargo fmt but all of these changes seem a bit much?
natboehm Oct 5, 2017
31331a4
add link to pending invites route
natboehm Oct 6, 2017
d771e1f
Run rustfmt-nightly 0.2.7
carols10cents Oct 6, 2017
78dffb7
Merge remote-tracking branch 'upstream/master' into require-owner-app…
carols10cents Oct 6, 2017
0264e76
Oops, introduced a formatting error in the merge resolution
carols10cents Oct 6, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/templates/application.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
{{#rl-dropdown tagName="ul" class="dropdown current-user-links" closeOnChildClick="a:link"}}
<li>{{#link-to 'dashboard'}}Dashboard{{/link-to}}</li>
<li>{{#link-to 'me'}}Account Settings{{/link-to}}</li>
<li>{{#link-to 'me.pending-invites'}}Owner Invites{{/link-to}}</li>
<li class='last'>{{#link-to 'logout'}}Sign Out{{/link-to}}</li>
{{/rl-dropdown}}
{{/rl-dropdown-container}}
Expand Down
10 changes: 8 additions & 2 deletions src/crate_owner_invitation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,10 @@ fn accept_invite(
conn: &PgConnection,
crate_invite: InvitationResponse,
) -> CargoResult<Response> {
let user_id = req.user()?.id;
use diesel::{delete, insert};
use diesel::pg::upsert::{do_update, OnConflictExtension};

let user_id = req.user()?.id;
let pending_crate_owner = crate_owner_invitations::table
.filter(crate_owner_invitations::crate_id.eq(crate_invite.crate_id))
.filter(crate_owner_invitations::invited_user_id.eq(user_id))
Expand All @@ -136,7 +138,11 @@ fn accept_invite(
};

conn.transaction(|| {
insert(&owner).into(crate_owners::table).execute(conn)?;
insert(&owner.on_conflict(
crate_owners::table.primary_key(),
do_update().set(crate_owners::deleted.eq(false)),
)).into(crate_owners::table)
.execute(conn)?;
delete(
crate_owner_invitations::table
.filter(crate_owner_invitations::crate_id.eq(crate_invite.crate_id))
Expand Down
76 changes: 59 additions & 17 deletions src/krate/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use download::{EncodableVersionDownload, VersionDownload};
use git;
use keyword::{CrateKeyword, EncodableKeyword};
use owner::{rights, CrateOwner, EncodableOwner, Owner, OwnerKind, Rights, Team};
use crate_owner_invitation::NewCrateOwnerInvitation;
use pagination::Paginate;
use render;
use schema::*;
Expand Down Expand Up @@ -460,22 +461,52 @@ impl Crate {
conn: &PgConnection,
req_user: &User,
login: &str,
) -> CargoResult<()> {
) -> CargoResult<String> {
use diesel::insert;

let owner = Owner::find_or_create_by_login(app, conn, req_user, login)?;

let crate_owner = CrateOwner {
crate_id: self.id,
owner_id: owner.id(),
created_by: req_user.id,
owner_kind: owner.kind() as i32,
};
diesel::insert(&crate_owner.on_conflict(
crate_owners::table.primary_key(),
do_update().set(crate_owners::deleted.eq(false)),
)).into(crate_owners::table)
.execute(conn)?;
match owner {
// Users are invited and must accept before being added
owner @ Owner::User(_) => {
let owner_invitation = NewCrateOwnerInvitation {
invited_user_id: owner.id(),
invited_by_user_id: req_user.id,
crate_id: self.id,
};

Ok(())
diesel::insert(&owner_invitation.on_conflict_do_nothing())
.into(crate_owner_invitations::table)
.execute(conn)?;

Ok(format!(
"user {} has been invited to be an owner of crate {}",
owner.login(),
self.name
))
}
// Teams are added as owners immediately
owner @ Owner::Team(_) => {
let crate_owner = CrateOwner {
crate_id: self.id,
owner_id: owner.id(),
created_by: req_user.id,
owner_kind: OwnerKind::Team as i32,
};

insert(&crate_owner.on_conflict(
crate_owners::table.primary_key(),
do_update().set(crate_owners::deleted.eq(false)),
)).into(crate_owners::table)
.execute(conn)?;

Ok(format!(
"team {} has been added as an owner of crate {}",
owner.login(),
self.name
))
}
}
}

pub fn owner_remove(
Expand Down Expand Up @@ -924,8 +955,10 @@ pub fn new(req: &mut Request) -> CargoResult<Response> {
let owners = krate.owners(&conn)?;
if rights(req.app(), &owners, &user)? < Rights::Publish {
return Err(human(
"crate name has already been claimed by \
another user",
"this crate exists but you don't seem to be an owner. \
If you believe this is a mistake, perhaps you need \
to accept an invitation to be an owner before \
publishing.",
));
}

Expand Down Expand Up @@ -1373,12 +1406,15 @@ fn modify_owners(req: &mut Request, add: bool) -> CargoResult<Response> {
.or(request.users)
.ok_or_else(|| human("invalid json request"))?;

let mut msgs = Vec::new();

for login in &logins {
if add {
if owners.iter().any(|owner| owner.login() == *login) {
return Err(human(&format_args!("`{}` is already an owner", login)));
}
krate.owner_add(req.app(), &conn, user, login)?;
let msg = krate.owner_add(req.app(), &conn, user, login)?;
msgs.push(msg);
} else {
// Removing the team that gives you rights is prevented because
// team members only have Rights::Publish
Expand All @@ -1389,11 +1425,17 @@ fn modify_owners(req: &mut Request, add: bool) -> CargoResult<Response> {
}
}

let comma_sep_msg = msgs.join(",");

#[derive(Serialize)]
struct R {
ok: bool,
msg: String,
}
Ok(req.json(&R { ok: true }))
Ok(req.json(&R {
ok: true,
msg: comma_sep_msg,
}))
}

/// Handles the `GET /crates/:crate_id/reverse_dependencies` route.
Expand Down
4 changes: 2 additions & 2 deletions src/tests/all.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ mod token;
mod user;
mod version;

#[derive(Deserialize)]
#[derive(Deserialize, Debug)]
struct GoodCrate {
#[serde(rename = "crate")] krate: EncodableCrate,
warnings: Warnings,
Expand All @@ -110,7 +110,7 @@ struct CrateList {
crates: Vec<EncodableCrate>,
meta: CrateMeta,
}
#[derive(Deserialize)]
#[derive(Deserialize, Debug)]
struct Warnings {
invalid_categories: Vec<String>,
invalid_badges: Vec<String>,
Expand Down
54 changes: 53 additions & 1 deletion src/tests/krate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -699,7 +699,9 @@ fn new_krate_wrong_user() {

let json = bad_resp!(middle.call(&mut req));
assert!(
json.errors[0].detail.contains("another user"),
json.errors[0]
.detail
.contains("this crate exists but you don't seem to be an owner.",),
"{:?}",
json.errors
);
Expand Down Expand Up @@ -2003,6 +2005,56 @@ fn block_blacklisted_documentation_url() {
assert_eq!(json.krate.documentation, None);
}

// This is testing Cargo functionality! ! !
// specifically functions modify_owners and add_owners
// which call the `PUT /crates/:crate_id/owners` route
#[test]
fn test_cargo_invite_owners() {
let (_b, app, middle) = ::app();
let mut req = ::req(app.clone(), Method::Get, "/");

let new_user = {
let conn = app.diesel_database.get().unwrap();
let owner = ::new_user("avocado").create_or_update(&conn).unwrap();
::sign_in_as(&mut req, &owner);
::CrateBuilder::new("guacamole", owner.id).expect_build(&conn);
::new_user("cilantro").create_or_update(&conn).unwrap()
};

#[derive(Serialize)]
struct OwnerReq {
owners: Option<Vec<String>>,
}
#[derive(Deserialize, Debug)]
struct OwnerResp {
ok: bool,
msg: String,
}

let body = serde_json::to_string(&OwnerReq {
owners: Some(vec![new_user.gh_login]),
});
let mut response = ok_resp!(
middle.call(
req.with_path("/api/v1/crates/guacamole/owners")
.with_method(Method::Put)
.with_body(body.unwrap().as_bytes()),
)
);

let r = ::json::<OwnerResp>(&mut response);
// this ok:true field is what old versions of Cargo
// need - do not remove unless you're cool with
// dropping support for old versions
assert!(r.ok);
// msg field is what is sent and used in updated
// version of cargo
assert_eq!(
r.msg,
"user cilantro has been invited to be an owner of crate guacamole"
)
}

// #[test]
// fn new_crate_bad_tarball() {
// let (_b, app, middle) = ::app();
Expand Down
Loading