Skip to content

Commit cfafa89

Browse files
committed
Auto merge of rust-lang#1622 - integer32llc:invalid_emails_from_github, r=sgrif
Invalid emails from GitHub This supersedes rust-lang#1251.
2 parents 1959fa4 + 8dae597 commit cfafa89

File tree

5 files changed

+109
-38
lines changed

5 files changed

+109
-38
lines changed

src/controllers/user/me.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::controllers::prelude::*;
33
use crate::controllers::helpers::Paginate;
44
use crate::email;
55
use crate::util::bad_request;
6+
use crate::util::errors::CargoError;
67

78
use crate::models::{Email, Follow, NewEmail, User, Version};
89
use crate::schema::{crates, emails, follows, users, versions};
@@ -127,7 +128,7 @@ pub fn update_user(req: &mut dyn Request) -> CargoResult<Response> {
127128
return Err(human("empty email rejected"));
128129
}
129130

130-
conn.transaction(|| {
131+
conn.transaction::<_, Box<dyn CargoError>, _>(|| {
131132
update(users.filter(gh_login.eq(&user.gh_login)))
132133
.set(email.eq(user_email))
133134
.execute(&*conn)?;
@@ -146,8 +147,9 @@ pub fn update_user(req: &mut dyn Request) -> CargoResult<Response> {
146147
.get_result::<String>(&*conn)
147148
.map_err(|_| human("Error in creating token"))?;
148149

149-
crate::email::send_user_confirm_email(user_email, &user.gh_login, &token)
150-
.map_err(|_| bad_request("Email could not be sent"))
150+
crate::email::send_user_confirm_email(user_email, &user.gh_login, &token);
151+
152+
Ok(())
151153
})?;
152154

153155
#[derive(Serialize)]
@@ -199,7 +201,7 @@ pub fn regenerate_token_and_send(req: &mut dyn Request) -> CargoResult<Response>
199201
.get_result::<Email>(&*conn)
200202
.map_err(|_| bad_request("Email could not be found"))?;
201203

202-
email::send_user_confirm_email(&email.email, &user.gh_login, &email.token)
204+
email::try_send_user_confirm_email(&email.email, &user.gh_login, &email.token)
203205
.map_err(|_| bad_request("Error in sending email"))
204206
})?;
205207

src/controllers/user/session.rs

Lines changed: 58 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::github;
44
use conduit_cookie::RequestSession;
55
use rand::{thread_rng, Rng};
66

7-
use crate::models::NewUser;
7+
use crate::models::{NewUser, User};
88

99
/// Handles the `GET /authorize_url` route.
1010
///
@@ -84,37 +84,76 @@ pub fn github_access_token(req: &mut dyn Request) -> CargoResult<Response> {
8484
}
8585
}
8686

87-
#[derive(Deserialize)]
88-
struct GithubUser {
89-
email: Option<String>,
90-
name: Option<String>,
91-
login: String,
92-
id: i32,
93-
avatar_url: Option<String>,
94-
}
95-
9687
// Fetch the access token from github using the code we just got
9788
let token = req.app().github.exchange(code).map_err(|s| human(&s))?;
9889

9990
let ghuser = github::github::<GithubUser>(req.app(), "/user", &token)?;
91+
let user = ghuser.save_to_database(&token.access_token, &*req.db_conn()?)?;
10092

101-
let user = NewUser::new(
102-
ghuser.id,
103-
&ghuser.login,
104-
ghuser.email.as_ref().map(|s| &s[..]),
105-
ghuser.name.as_ref().map(|s| &s[..]),
106-
ghuser.avatar_url.as_ref().map(|s| &s[..]),
107-
&token.access_token,
108-
)
109-
.create_or_update(&*req.db_conn()?)?;
11093
req.session()
11194
.insert("user_id".to_string(), user.id.to_string());
11295
req.mut_extensions().insert(user);
11396
super::me::me(req)
11497
}
11598

99+
#[derive(Deserialize)]
100+
struct GithubUser {
101+
email: Option<String>,
102+
name: Option<String>,
103+
login: String,
104+
id: i32,
105+
avatar_url: Option<String>,
106+
}
107+
108+
impl GithubUser {
109+
fn save_to_database(&self, access_token: &str, conn: &PgConnection) -> QueryResult<User> {
110+
Ok(NewUser::new(
111+
self.id,
112+
&self.login,
113+
self.email.as_ref().map(|s| &s[..]),
114+
self.name.as_ref().map(|s| &s[..]),
115+
self.avatar_url.as_ref().map(|s| &s[..]),
116+
access_token,
117+
)
118+
.create_or_update(conn)?)
119+
}
120+
}
121+
116122
/// Handles the `GET /logout` route.
117123
pub fn logout(req: &mut dyn Request) -> CargoResult<Response> {
118124
req.session().remove(&"user_id".to_string());
119125
Ok(req.json(&true))
120126
}
127+
128+
#[cfg(test)]
129+
mod tests {
130+
use super::*;
131+
use dotenv::dotenv;
132+
use std::env;
133+
134+
fn pg_connection() -> PgConnection {
135+
let _ = dotenv();
136+
let database_url =
137+
env::var("TEST_DATABASE_URL").expect("TEST_DATABASE_URL must be set to run tests");
138+
PgConnection::establish(&database_url).unwrap()
139+
}
140+
141+
#[test]
142+
fn gh_user_with_invalid_email_doesnt_fail() {
143+
let conn = pg_connection();
144+
let gh_user = GithubUser {
145+
email: Some("String.Format(\"{0}.{1}@live.com\", FirstName, LastName)".into()),
146+
name: Some("My Name".into()),
147+
login: "github_user".into(),
148+
id: -1,
149+
avatar_url: None,
150+
};
151+
let result = gh_user.save_to_database("arbitrary_token", &conn);
152+
153+
assert!(
154+
result.is_ok(),
155+
"Creating a User from a GitHub user failed when it shouldn't have, {:?}",
156+
result
157+
);
158+
}
159+
}

src/email.rs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,22 @@ fn build_email(
5757
Ok(email.into())
5858
}
5959

60-
pub fn send_user_confirm_email(email: &str, user_name: &str, token: &str) -> CargoResult<()> {
60+
/// Attempts to send a confirmation email. Swallows all errors.
61+
///
62+
/// This function swallows any errors that occur while attempting to send the email. Some users
63+
/// have an invalid email set in their GitHub profile, and we should let them sign in even though
64+
/// we're trying to silently use their invalid address during signup and can't send them an email.
65+
/// Use `try_send_user_confirm_email` when the user is directly trying to set their email.
66+
pub fn send_user_confirm_email(email: &str, user_name: &str, token: &str) {
67+
let _ = try_send_user_confirm_email(email, user_name, token);
68+
}
69+
70+
/// Attempts to send a confirmation email and returns errors.
71+
///
72+
/// For use in cases where we want to fail if an email is bad because the user is directly trying
73+
/// to set their email correctly, as opposed to us silently trying to use the email from their
74+
/// GitHub profile during signup.
75+
pub fn try_send_user_confirm_email(email: &str, user_name: &str, token: &str) -> CargoResult<()> {
6176
// Create a URL with token string as path to send to user
6277
// If user clicks on path, look email/user up in database,
6378
// make sure tokens match
@@ -100,3 +115,24 @@ fn send_email(recipient: &str, subject: &str, body: &str) -> CargoResult<()> {
100115

101116
Ok(())
102117
}
118+
119+
#[cfg(test)]
120+
mod tests {
121+
use super::*;
122+
123+
#[test]
124+
fn sending_to_invalid_email_fails() {
125+
let result = send_email(
126+
"String.Format(\"{0}.{1}@live.com\", FirstName, LastName)",
127+
"test",
128+
"test",
129+
);
130+
assert!(result.is_err());
131+
}
132+
133+
#[test]
134+
fn sending_to_valid_email_succeeds() {
135+
let result = send_email("[email protected]", "test", "test");
136+
assert!(result.is_ok());
137+
}
138+
}

src/models/user.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ impl<'a> NewUser<'a> {
5858
use diesel::insert_into;
5959
use diesel::pg::upsert::excluded;
6060
use diesel::sql_types::Integer;
61-
use diesel::NotFound;
6261

6362
conn.transaction(|| {
6463
let user = insert_into(users)
@@ -96,8 +95,7 @@ impl<'a> NewUser<'a> {
9695
.optional()?;
9796

9897
if let Some(token) = token {
99-
crate::email::send_user_confirm_email(user_email, &user.gh_login, &token)
100-
.map_err(|_| NotFound)?;
98+
crate::email::send_user_confirm_email(user_email, &user.gh_login, &token);
10199
}
102100
}
103101

src/tests/user.rs

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -270,10 +270,7 @@ fn test_github_login_does_not_overwrite_email() {
270270
let mut req = req(Method::Get, "/api/v1/me");
271271
let user = {
272272
let conn = app.diesel_database.get().unwrap();
273-
let user = NewUser {
274-
gh_id: 1,
275-
..new_user("apricot")
276-
};
273+
let user = new_user("apricot");
277274

278275
let user = user.create_or_update(&conn).unwrap();
279276
sign_in_as(&mut req, &user);
@@ -299,7 +296,7 @@ fn test_github_login_does_not_overwrite_email() {
299296
{
300297
let conn = app.diesel_database.get().unwrap();
301298
let user = NewUser {
302-
gh_id: 1,
299+
gh_id: user.gh_id,
303300
..new_user("apricot")
304301
};
305302

@@ -449,17 +446,17 @@ fn test_this_user_cannot_change_that_user_email() {
449446
fn test_insert_into_email_table() {
450447
let (_b, app, middle) = app();
451448
let mut req = req(Method::Get, "/me");
452-
{
449+
let user = {
453450
let conn = app.diesel_database.get().unwrap();
454451
let user = NewUser {
455-
gh_id: 1,
456452
email: Some("[email protected]"),
457453
..new_user("apple")
458454
};
459455

460456
let user = user.create_or_update(&conn).unwrap();
461457
sign_in_as(&mut req, &user);
462-
}
458+
user
459+
};
463460

464461
let mut response = ok_resp!(middle.call(req.with_path("/api/v1/me").with_method(Method::Get),));
465462
let r = crate::json::<UserShowPrivateResponse>(&mut response);
@@ -472,7 +469,7 @@ fn test_insert_into_email_table() {
472469
{
473470
let conn = app.diesel_database.get().unwrap();
474471
let user = NewUser {
475-
gh_id: 1,
472+
gh_id: user.gh_id,
476473
email: Some("[email protected]"),
477474
..new_user("apple")
478475
};
@@ -499,7 +496,6 @@ fn test_insert_into_email_table_with_email_change() {
499496
let user = {
500497
let conn = app.diesel_database.get().unwrap();
501498
let user = NewUser {
502-
gh_id: 1,
503499
email: Some("[email protected]"),
504500
..new_user("potato")
505501
};
@@ -531,7 +527,7 @@ fn test_insert_into_email_table_with_email_change() {
531527
{
532528
let conn = app.diesel_database.get().unwrap();
533529
let user = NewUser {
534-
gh_id: 1,
530+
gh_id: user.gh_id,
535531
email: Some("[email protected]"),
536532
..new_user("potato")
537533
};

0 commit comments

Comments
 (0)