Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upWIP: Teams #177
Conversation
This comment has been minimized.
This comment has been minimized.
|
Oh, closes #137 |
This comment has been minimized.
This comment has been minimized.
Gankro
force-pushed the
Gankro:teams
branch
2 times, most recently
from
739d162
to
6ee2bef
Aug 6, 2015
This comment has been minimized.
This comment has been minimized.
|
All old tests pass locally now. Moving on to making new tests. |
Gankro
reviewed
Aug 6, 2015
| @@ -174,8 +175,9 @@ impl Crate { | |||
| }))); | |||
|
|
|||
| try!(conn.execute("INSERT INTO crate_owners | |||
| (crate_id, user_id, created_at, updated_at, deleted) | |||
| VALUES ($1, $2, $3, $3, FALSE)", | |||
| (crate_id, owner_id, created_by, created_at, | |||
This comment has been minimized.
This comment has been minimized.
Gankro
Aug 6, 2015
Author
Contributor
Note: I added the created_by field here while debugging a test failure cause by something else. Not sure why it wasn't being set and whether it should be set.
Gankro
reviewed
Aug 6, 2015
|
|
||
| #[derive(RustcEncodable)] | ||
| struct R { users: Vec<EncodableUser> } | ||
| struct R { users: Vec<EncodableOwner> } |
This comment has been minimized.
This comment has been minimized.
Gankro
Aug 6, 2015
Author
Contributor
Note: this is the one place that I'm not sure how to handle API-wise -- this is something that cargo sends us. I suppose we could have two duplicate fields, both of which are Options? Try to decode both, pick whichever has stuff first?
This comment has been minimized.
This comment has been minimized.
alexcrichton
Aug 11, 2015
Member
Yeah for now let's have two duplicate fields, both options. We can slowly phase out the older field later on down the road (could add a comment explaining what's up)
Gankro
referenced this pull request
Aug 7, 2015
Closed
Groups for ownership *or* GitHub-based permissions #137
This comment has been minimized.
This comment has been minimized.
|
Status Update: I have tested this out locally by hand and everything seems to be working!!! I just need to write some unit tests. (oh nooo mocking) |
alexcrichton
referenced this pull request
Aug 11, 2015
Merged
Second try making the graph legend fit without wrap #183
alexcrichton
reviewed
Aug 11, 2015
| VALUES ($1, $2, $3, $3, FALSE)", | ||
| (crate_id, owner_id, created_by, created_at, | ||
| updated_at, deleted, owner_kind) | ||
| VALUES ($1, $2, $2, $3, $3, FALSE, 0)", |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Aug 11, 2015
Member
Could you refactor this literal 0 into something like OwnerKind::User as i32?
alexcrichton
reviewed
Aug 11, 2015
| pub fn owners(&self, conn: &Connection) -> CargoResult<Vec<User>> { | ||
| pub fn owners(&self, conn: &Connection) -> CargoResult<Vec<Owner>> { | ||
| // Users and Teams are differentiated by the owner_kind field: | ||
| // User = 0, Team = 1 |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
alexcrichton
reviewed
Aug 11, 2015
| /// Unifies the notion of a User or a Team. | ||
| pub enum Owner { | ||
| User(User), | ||
| Team(Team) |
This comment has been minimized.
This comment has been minimized.
alexcrichton
reviewed
Aug 11, 2015
| /// ask github for it. *shrug* | ||
| github_id: i32, | ||
| /// Unique table id | ||
| cargo_id: i32, |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Aug 11, 2015
Member
Currently the model-like structs all have public fields, and the crates.io ID is just called id
alexcrichton
reviewed
Aug 11, 2015
| pub id: i32, | ||
| // Login is deprecated in favour of name, but needs to be printed for back-compat | ||
| pub login: String, | ||
| pub kind: String, |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Gankro
Aug 11, 2015
Author
Contributor
I think this is only ever encoded today. Cargo only ever sends us a list of names.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Gankro
Aug 11, 2015
Author
Contributor
Yes, cargo will decode this -- but we won't. So we say we have to provide the login, and downstream Cargo can say it's optional if it wants. But we have to for old Cargos.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
alexcrichton
Aug 11, 2015
Member
Oh sorry I meant the kind field. Cargo doesn't currently give us a kind field, right? That means this'll have to be an Option if we want to continue to receive responses from Cargo?
This comment has been minimized.
This comment has been minimized.
Gankro
Aug 11, 2015
Author
Contributor
Yeah so due to the github: prefix owners are uniquely identified by their string name -- which is the only way Cargo talks to us about them. They just send a list of names (which isn't parsed with this struct at all).
alexcrichton
reviewed
Aug 11, 2015
| ), | ||
| Migration::add_table(20150804170128, "teams", " | ||
| id SERIAL PRIMARY KEY, | ||
| name VARCHAR NOT NULL UNIQUE, |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Aug 11, 2015
Member
Can you make sure there's an index on this field? You're doing a query on it sometimes so it'll need to be indexed, although I forget if the UNIQUE constraint adds an index or not...
This comment has been minimized.
This comment has been minimized.
Gankro
Aug 11, 2015
Author
Contributor
Per http://dba.stackexchange.com/questions/144/when-should-i-use-a-unique-constraint-instead-of-a-unique-index it appears that a unique constraint implies the creation of an index. The top answer argues you should favour an index if you actually intend to index off that value as a matter of clarity (the index won't go away if the uniqueness constraint goes away), but that doesn't seem like a concern here.
This comment has been minimized.
This comment has been minimized.
alexcrichton
reviewed
Aug 11, 2015
| match chunks.next().unwrap() { | ||
| // github:rust-lang:owners | ||
| "github" => { | ||
| // Ok to unwrap since we know one ":" is contained |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Gankro
Aug 11, 2015
Author
Contributor
This is actually going to revert back -- the try! on org is otherwise dead code.
alexcrichton
reviewed
Aug 11, 2015
| 'a'...'z' | 'A'...'Z' | '0'...'9' | '-' | '_' => false, | ||
| _ => true | ||
| } | ||
| } |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Gankro
Aug 11, 2015
Author
Contributor
That doesn't seem very semantically clear. We're trying to filter out to only the valid set of GH team slugs. (actually I think A...Z could be omitted)
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Gankro
Aug 11, 2015
Author
Contributor
Definitely want to sanitize to avoid /'s and what not -- easiest way I could think of was to just whitelist the stuff that's in slugs.
This comment has been minimized.
This comment has been minimized.
alexcrichton
Aug 11, 2015
Member
Hm ok, I'll probably do one final auditing pass but this is fine for now
alexcrichton
reviewed
Aug 11, 2015
| .header("Accept", "application/vnd.github.v3+json") | ||
| .header("User-Agent", "hello!") | ||
| .header("Authorization", &format!("token {}", &req_user.gh_access_token)) | ||
| .exec()); |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Aug 11, 2015
Member
Could you refactor this between src/user/mod.rs to avoid duplication?
This comment has been minimized.
This comment has been minimized.
alexcrichton
Aug 11, 2015
Member
Unfortunately this may also not be quite right as it looks like the teams listing for an organization has pagination, so we may have to make multiple requests to look up the teams in an organization.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
alexcrichton
Aug 11, 2015
Member
Setting up the http::handle, and various other headers are duplicated, and it'd be useful to just do something like passing in the URL and then getting the response.
This comment has been minimized.
This comment has been minimized.
Gankro
Aug 11, 2015
Author
Contributor
So one thing is that Teams and Users auth in different ways. Users are using the oauth2 lib to inject the header, but Teams can't do that (as far as I can tell) because there isn't a way to create an oauth2 object from the string key we store. As such teams just write the appropriate header manually (which is trivial and will never change).
alexcrichton
reviewed
Aug 11, 2015
| VALUES ($1, $2)", | ||
| &[&name, &github_id])); | ||
|
|
||
| // read it right back out: |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Aug 11, 2015
Member
If you search for "RETURNING" you can see a method of inserting while also returning the row that was just inserted
This comment has been minimized.
This comment has been minimized.
alexcrichton
reviewed
Aug 11, 2015
| crate. You may need to re-authenticate on \ | ||
| crates.io to grant permission to read \ | ||
| github org memberships. Just go to \ | ||
| https://crates.io/login")); |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Aug 11, 2015
Member
It'd be good to refactor this 403-checking logic with the above block to reduce duplication
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Gankro
Aug 11, 2015
Author
Contributor
I think it's kinda important from a UX perspective -- people are going to try this feature and get walled on out-of-date authentication. There's two places where this can happen, and they're sufficiently different that they warrant different messages IMO.
This comment has been minimized.
This comment has been minimized.
alexcrichton
Aug 11, 2015
Member
Hm ok, can there perhaps be a switch of some form? It'd just be nice to consolidate all this HTTP logic as much as possible.
alexcrichton
reviewed
Aug 11, 2015
| })); | ||
|
|
||
| // mock Team (only need ID to check team status) | ||
| let team = Team { github_id: github_id, cargo_id: 0, name: String::new() }; |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Aug 11, 2015
Member
Instead of making a dummy team could this just be a static method which takes only a github id?
alexcrichton
reviewed
Aug 11, 2015
| /// to this method, we may still succeed if Github returns us the same ID | ||
| /// as the One True Name. However in this case, the One True Name will | ||
| /// still be selected. | ||
| pub fn find_by_name_for_add(conn: &Connection, name: &str, req_user: &User) |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Aug 11, 2015
Member
This is only called in one location, so perhaps the logic for checking whether the user is a member of a team can be inlined at that location? Either that or otherwise add a method to Owner querying whether a user is a member of that owner.
This comment has been minimized.
This comment has been minimized.
Gankro
Aug 11, 2015
Author
Contributor
What's the purpose of the ownership query? (Old drafts had such a method, but it ended up being useless).
This comment has been minimized.
This comment has been minimized.
alexcrichton
Aug 11, 2015
Member
Just in the sense that the caller of this method would do something like:
let owner = try!(Owner::find_by_name(name));
if !owner.contains(&user) {
return Err(...)
}
This comment has been minimized.
This comment has been minimized.
Gankro
Aug 11, 2015
Author
Contributor
Is this not the moral equivalent of User::find_or_insert (but generalized)?
alexcrichton
reviewed
Aug 11, 2015
| email: None, | ||
| avatar: None, | ||
| name: None, | ||
| kind: String::from("owner"), |
This comment has been minimized.
This comment has been minimized.
alexcrichton
reviewed
Aug 11, 2015
| login: name, | ||
| email: None, | ||
| avatar: None, | ||
| name: None, |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Gankro
Aug 11, 2015
Author
Contributor
ok I have this back in cache now. name is the pretty-printed-name. e.g. "Alexis Beingessner". So it doesn't super make sense to produce that here? (it will make cargo produce a silly output when you list owners).
We do want a duplicate of login, it's unfortunate that name is taken!
This comment has been minimized.
This comment has been minimized.
Gankro
force-pushed the
Gankro:teams
branch
from
57b53e7
to
2fc417f
Aug 11, 2015
alexcrichton
reviewed
Aug 14, 2015
|
|
||
| pub fn github(app: &App, url: &str, auth: &Token) | ||
| -> Result<curl::http::Response, curl::ErrCode> { | ||
| println!("HTTP: {}", url); |
This comment has been minimized.
This comment has been minimized.
alexcrichton
reviewed
Aug 14, 2015
| .exec() | ||
| } | ||
|
|
||
| pub fn token(token: String) -> Token { |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Aug 14, 2015
Member
Could you add a comment here indicating that this is intended to pass to the function above? (e.g. the scopes/token_type are blank)
alexcrichton
reviewed
Aug 14, 2015
| human(format!("could not find user with login `{}`", name)) | ||
| })); | ||
| try!(conn.execute("UPDATE crate_owners | ||
| SET deleted = TRUE, updated_at = $1 | ||
| WHERE crate_id = $2 AND user_id = $3", | ||
| &[&::now(), &self.id, &user.id])); | ||
| WHERE crate_id = $2 AND owner_id = $3 AND owner_kind = $4", |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Aug 14, 2015
Member
You shouldn't need the owner_kind field here, owner_id should be unique and should directly imply what owner_kind should be
This comment has been minimized.
This comment has been minimized.
alexcrichton
reviewed
Aug 14, 2015
| @@ -476,13 +505,15 @@ pub fn index(req: &mut Request) -> CargoResult<Response> { | |||
| (format!("SELECT crates.* FROM crates | |||
| INNER JOIN crate_owners | |||
| ON crate_owners.crate_id = crates.id | |||
| WHERE crate_owners.user_id = $1 {} \ | |||
| WHERE crate_owners.owner_id = $1 | |||
| AND crate_owners.owner_kind = 0 {} | |||
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
alexcrichton
reviewed
Aug 14, 2015
|
|
||
| let resp = try!(http::github(app, | ||
| &format!("http://api.github.com/teams/{}/memberships/{}", &github_id, &user.gh_login), | ||
| &http::token(user.gh_access_token.clone()))); |
This comment has been minimized.
This comment has been minimized.
alexcrichton
reviewed
Aug 14, 2015
| } | ||
| } | ||
|
|
||
| fn team_with_gh_id_contains_user(app: &App, github_id: i32, user: &User) -> CargoResult<bool> { |
This comment has been minimized.
This comment has been minimized.
alexcrichton
reviewed
Aug 14, 2015
| body.extend(json.as_bytes().iter().cloned()); | ||
| body.extend([0, 0, 0, 0].iter().cloned()); | ||
| body | ||
| } |
This comment has been minimized.
This comment has been minimized.
alexcrichton
reviewed
Aug 15, 2015
| assert!(json.errors[0].detail.contains("don't have permission"), | ||
| "{:?}", json.errors); | ||
| } | ||
|
|
This comment has been minimized.
This comment has been minimized.
alexcrichton
Aug 15, 2015
Member
Can you also add tests for:
- Owners can remove a team from a crate
- Team members cannot remove a team from a crate
alexcrichton
reviewed
Aug 15, 2015
| @@ -343,7 +344,7 @@ fn modify_yank(req: &mut Request, yanked: bool) -> CargoResult<Response> { | |||
| let user = try!(req.user()); | |||
| let tx = try!(req.tx()); | |||
| let owners = try!(krate.owners(tx)); | |||
| if !owners.iter().any(|u| u.id == user.id) { | |||
| if try!(rights(req.app(), &owners, &user)) < Rights::Publish { | |||
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Gankro
Aug 17, 2015
Author
Contributor
Team members have Rights::Publish, so they should pass this check.
alexcrichton
reviewed
Aug 15, 2015
| id: id, | ||
| login: name, | ||
| email: None, | ||
| avatar: None, |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Aug 15, 2015
Member
Can you make sure the frontend handles this nicely? I'm curious what the page looks like when it lists owners and one of the owners is a team.
This comment has been minimized.
This comment has been minimized.
Gankro
Aug 17, 2015
Author
Contributor
You get an output that looks like:
steveklabnik (Steve Klabnik <steve@steveklabnik.com>)
github:rust-lang:owners
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Gankro
Aug 17, 2015
Author
Contributor
The frontend really drops the ball on this, I'm not gonna lie. Broken image and a link to github.com/github:rust-lang:owners
alexcrichton
reviewed
Aug 15, 2015
| use oauth2::*; | ||
| use app::App; | ||
|
|
||
| pub fn github(app: &App, url: &str, auth: &Token) |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Aug 15, 2015
Member
This function might be a lot nicer to call if it was something along the lines of:
pub fn github<T: Decodable>(app: &App, url: &str, auth: &Token)
-> Result<T, curl::ErrCode> {
// ...
}Basically something that does the decoding for you automatically and fills in an error for bad JSON and bad utf-8-ness
This comment has been minimized.
This comment has been minimized.
Gankro
Aug 17, 2015
Author
Contributor
The response-code handling isn't uniform, though. In particular when querying team membership, 404 is how Github reports "false". Do you want this to take a closure for (maybe) processing the error code?
This comment has been minimized.
This comment has been minimized.
Gankro
Aug 17, 2015
Author
Contributor
I made two fns, one for querying, one for parsing, so that you can intermediate evaluation of the request as desired.
This comment has been minimized.
This comment has been minimized.
|
OK, I just hooked up coveralls to this repo so in theory when you rebase you'll start seeing coverage information published, and it's a good check to see if at least most of the branches and such were taken during the tests perhaps? |
Gankro
added some commits
Jul 16, 2015
Gankro
force-pushed the
Gankro:teams
branch
from
da16e83
to
cfb23d5
Aug 18, 2015
This comment has been minimized.
This comment has been minimized.
|
I didn't mean to revert the per_page thing -- you may want to fix this |
alexcrichton
merged commit e5ace68
into
rust-lang:master
Aug 18, 2015
This comment has been minimized.
This comment has been minimized.
|
Awesome, thanks so much @Gankro! I've added some extra things here and there and pushed to master. |
Gankro commentedAug 6, 2015
This is completely untested
I just got it to compile, and wanted to get this up for the bikesheddy part of review while I start setting up testing and stuff.
Some of the JSON schemas still probably need to be updated to yield duplicate fields for bridging legacy and clarity. I don't think Cargo needs to be modified at all (teams are Just Another Name as far as it should be concerned), but I might be forgetting something.