webfw_iron.rs 49.1 KB
Newer Older
Robert Czechowski's avatar
Robert Czechowski committed
1
2
use std::path::Path;

Robert Czechowski's avatar
Robert Czechowski committed
3
4
5
pub use handlebars_iron::handlebars::to_json;
use handlebars_iron::{DirectorySource, HandlebarsEngine, Template};
use iron;
Robert Czechowski's avatar
Robert Czechowski committed
6
use iron::modifiers::Redirect;
7
use iron::modifiers::RedirectRaw;
Robert Czechowski's avatar
Robert Czechowski committed
8
9
10
use iron::prelude::*;
use iron::{status, AfterMiddleware, AroundMiddleware, Handler};
use iron_sessionstorage;
Robert Czechowski's avatar
Robert Czechowski committed
11
use iron_sessionstorage::backends::SignedCookieBackend;
Robert Czechowski's avatar
Robert Czechowski committed
12
use iron_sessionstorage::traits::*;
Robert Czechowski's avatar
Robert Czechowski committed
13
use iron_sessionstorage::SessionStorage;
Robert Czechowski's avatar
Robert Czechowski committed
14
use mount::Mount;
15
use persistent::{Read, Write};
16
use reqwest;
Robert Czechowski's avatar
Robert Czechowski committed
17
18
19
use router::Router;
use staticfile::Static;
use urlencoded::{UrlEncodedBody, UrlEncodedQuery};
20

Robert Czechowski's avatar
Robert Czechowski committed
21
22
#[cfg(feature = "debug")]
use iron::BeforeMiddleware;
Robert Czechowski's avatar
Robert Czechowski committed
23

24
use config::Config;
25
use core;
Robert Czechowski's avatar
Robert Czechowski committed
26
27
28
use db_conn::MedalConnection;
use iron::typemap::Key;
pub use serde_json::value as json_val;
29

30
static TASK_DIR: &str = "tasks";
Robert Czechowski's avatar
Robert Czechowski committed
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

macro_rules! mime {
    ($top:tt / $sub:tt) => (
        mime!($top / $sub;)
    );

    ($top:tt / $sub:tt ; $($attr:tt = $val:tt),*) => (
        iron::mime::Mime(
            iron::mime::TopLevel::$top,
            iron::mime::SubLevel::$sub,
            vec![ $((Attr::$attr,Value::$val)),* ]
        )
    );
}

46
macro_rules! with_conn {
47
    ( $x:expr , $c:ident, $r:expr , $($y:expr),* ) => {
48
        {
49
            let mutex = $r.get::<Write<SharedDatabaseConnection<$c>>>().unwrap();
50
51
52
53
54
            let conn = mutex.lock().unwrap_or_else(|e| e.into_inner());
            $x(&*conn, $($y),*)
        }
    };
}
Robert Czechowski's avatar
Robert Czechowski committed
55

56
macro_rules! template_ok {
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
57
58
59
60
61
62
63
    ( $x:expr ) => {{
        let (template, data) = $x;

        let mut resp = Response::new();
        resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
        Ok(resp)
    }};
64
65
}

66
/** Show error messages on commandline */
Robert Czechowski's avatar
Robert Czechowski committed
67
68
69
70
71
72
73
74
struct ErrorReporter;
impl AfterMiddleware for ErrorReporter {
    fn catch(&self, _: &mut Request, err: IronError) -> IronResult<Response> {
        println!("{}", err);
        Err(err)
    }
}

75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
/** Show error messages to users */
struct ErrorShower;
impl AfterMiddleware for ErrorShower {
    fn catch(&self, _: &mut Request, err: IronError) -> IronResult<Response> {
        let IronError { error, response } = err;
        if response.body.is_none() {
            Ok(match response.status {
                Some(s) => {
                    let n = s.to_u16();
                    if n >= 400 && n <= 599 {
                        response.set((mime!(Text / Html),
                                      format!("<h1>{} {}</h1>", n, s.canonical_reason().unwrap_or("(Unknown error)"))))
                    } else {
                        response
                    }
                }
                _ => response,
            })
        } else {
            Err(IronError { error, response })
        }
    }
}

Robert Czechowski's avatar
Robert Czechowski committed
99
#[derive(Debug)]
Robert Czechowski's avatar
Robert Czechowski committed
100
struct SessionToken {
Robert Czechowski's avatar
Robert Czechowski committed
101
    token: String,
Robert Czechowski's avatar
Robert Czechowski committed
102
103
104
105
106
107
108
109
110
111
112
113
114
}
impl iron_sessionstorage::Value for SessionToken {
    fn get_key() -> &'static str { "medal_session" }
    fn into_raw(self) -> String { self.token }
    fn from_raw(value: String) -> Option<Self> {
        if value.is_empty() {
            None
        } else {
            Some(SessionToken { token: value })
        }
    }
}

115
116
117
pub struct CookieDistributor {}

impl AroundMiddleware for CookieDistributor {
118
    fn around(self, handler: Box<dyn Handler>) -> Box<dyn Handler> {
119
120
        use rand::{distributions::Alphanumeric, thread_rng, Rng};

121
        Box::new(move |req: &mut Request| -> IronResult<Response> {
122
            if req.session().get::<SessionToken>().expect("blub...").is_none() {
123
124
125
126
127
128
129
130
                let session_token: String = thread_rng().sample_iter(&Alphanumeric).take(10).collect();
                req.session().set(SessionToken { token: session_token }).unwrap();
            }
            handler.handle(req)
        })
    }
}

Robert Czechowski's avatar
Robert Czechowski committed
131
#[cfg(feature = "debug")]
132
133
pub struct RequestLogger {}

Robert Czechowski's avatar
Robert Czechowski committed
134
#[cfg(feature = "debug")]
135
136
137
138
139
140
141
142
impl BeforeMiddleware for RequestLogger {
    fn before(&self, req: &mut Request) -> IronResult<()> {
        println!("{}: {}", req.method, req.url);

        Ok(())
    }
}

143
144
#[derive(Debug)]
struct SessionError {
Robert Czechowski's avatar
Robert Czechowski committed
145
    message: String,
146
147
}
impl ::std::error::Error for SessionError {
Robert Czechowski's avatar
Robert Czechowski committed
148
    fn description(&self) -> &str { &self.message }
149
150
151
}

impl ::std::fmt::Display for SessionError {
Robert Czechowski's avatar
Robert Czechowski committed
152
    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { write!(f, "{}", self.message) }
153
154
}

155
156
157
158
trait RequestSession {
    fn get_session_token(&mut self) -> Option<String>;
    fn require_session_token(&mut self) -> IronResult<String>;
    fn expect_session_token(&mut self) -> IronResult<String>;
159
160
}

161
162
impl<'a, 'b> RequestSession for Request<'a, 'b> {
    fn get_session_token(self: &mut Self) -> Option<String> {
163
        let session_token = self.session().get::<SessionToken>().unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
164
        (|st: Option<SessionToken>| -> Option<String> { Some(st?.token) })(session_token)
165
166
167
    }

    fn require_session_token(self: &mut Self) -> IronResult<String> {
168
        match self.session().get::<SessionToken>().unwrap() {
169
170
            Some(SessionToken { token: session }) => Ok(session),
            _ => {
Robert Czechowski's avatar
Robert Czechowski committed
171
                use rand::{distributions::Alphanumeric, thread_rng, Rng};
172

Daniel Brüning's avatar
Daniel Brüning committed
173
                let new_session_key: String = thread_rng().sample_iter(&Alphanumeric).take(28).collect();
174
                self.session().set(SessionToken { token: new_session_key }).unwrap();
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
175
176
177
178
179
180
181
182
183
                Err(IronError {
                    error: Box::new(SessionError {
                        message: "No valid session found, redirecting to cookie page".to_string(),
                    }),
                    response: Response::with((
                        status::Found,
                        RedirectRaw(format!("/cookie?{}", self.url.path().join("/"))),
                    )),
                })
184
185
186
187
188
            }
        }
    }

    fn expect_session_token(self: &mut Self) -> IronResult<String> {
189
        match self.session().get::<SessionToken>().unwrap() {
190
            Some(SessionToken { token: session }) => Ok(session),
Robert Czechowski's avatar
Robert Czechowski committed
191
192
193
            _ => Err(IronError { error: Box::new(SessionError { message:
                                                                    "No valid session found, access denied".to_string() }),
                                 response: Response::with(status::Forbidden) }),
194
195
196
197
198
199
200
201
        }
    }
}

trait RequestRouterParam {
    fn get_str(self: &mut Self, key: &str) -> Option<String>;
    fn get_int<T: ::std::str::FromStr>(self: &mut Self, key: &str) -> Option<T>;
    fn expect_int<T: ::std::str::FromStr>(self: &mut Self, key: &str) -> IronResult<T>;
202
    fn expect_str(self: &mut Self, key: &str) -> IronResult<String>;
203
204
205
206
207
208
209
210
211
212
}

impl<'a, 'b> RequestRouterParam for Request<'a, 'b> {
    fn get_str(self: &mut Self, key: &str) -> Option<String> {
        Some(self.extensions.get::<Router>()?.find(key)?.to_owned())
    }

    fn get_int<T: ::std::str::FromStr>(self: &mut Self, key: &str) -> Option<T> {
        Some(self.extensions.get::<Router>()?.find(key)?.parse::<T>().ok()?)
    }
213

214
215
216
    fn expect_int<T: ::std::str::FromStr>(self: &mut Self, key: &str) -> IronResult<T> {
        match self.get_int::<T>(key) {
            Some(i) => Ok(i),
Robert Czechowski's avatar
Robert Czechowski committed
217
218
219
            _ => Err(IronError { error: Box::new(SessionError { message:
                                                                    "No valid routing parameter".to_string() }),
                                 response: Response::with(status::Forbidden) }),
220
221
        }
    }
222

223
224
225
226
227
228
229
230
    fn expect_str(self: &mut Self, key: &str) -> IronResult<String> {
        match self.get_str(key) {
            Some(s) => Ok(s),
            _ => Err(IronError { error: Box::new(SessionError { message:
                                                                    "Routing parameter missing".to_string() }),
                                 response: Response::with(status::Forbidden) }),
        }
    }
231
232
}

233
struct AugMedalError<'c, 'a: 'c, 'b: 'c + 'a>(core::MedalError, &'c mut Request<'a, 'b>);
234
235
236
237

impl<'c, 'a, 'b> From<AugMedalError<'c, 'a, 'b>> for IronError {
    fn from(AugMedalError(me, req): AugMedalError<'c, 'a, 'b>) -> Self {
        match me {
238
            core::MedalError::NotLoggedIn => {
Robert Czechowski's avatar
Robert Czechowski committed
239
240
241
242
243
                IronError { error: Box::new(SessionError { message:
                                                               "Not Logged in, redirecting to login page".to_string() }),
                            response: Response::with((status::Found,
                                                      RedirectRaw(format!("/login?{}", req.url.path().join("/"))))) }
            }
244
245
246
247
248
249
250
            core::MedalError::AccessDenied => IronError { error: Box::new(SessionError { message:
                                                                                             "Access denied".to_string() }),
                                                          response: Response::with(status::Unauthorized) },
            core::MedalError::CsrfCheckFailed => IronError { error: Box::new(SessionError { message:
                                                                                                "CSRF Error".to_string() }),
                                                             response: Response::with(status::Forbidden) },
            core::MedalError::SessionTimeout => {
Robert Czechowski's avatar
Robert Czechowski committed
251
252
253
                IronError { error: Box::new(SessionError { message: "Session timed out".to_string() }),
                            response: Response::with(status::Forbidden) }
            }
254
            core::MedalError::DatabaseError => {
Robert Czechowski's avatar
Robert Czechowski committed
255
                IronError { error: Box::new(SessionError { message: "Database Error".to_string() }),
256
                            response: Response::with(status::InternalServerError) }
Robert Czechowski's avatar
Robert Czechowski committed
257
            }
258
            core::MedalError::PasswordHashingError => {
Robert Czechowski's avatar
Robert Czechowski committed
259
                IronError { error: Box::new(SessionError { message: "Error hashing the passwords".to_string() }),
260
                            response: Response::with(status::InternalServerError) }
Robert Czechowski's avatar
Robert Czechowski committed
261
            }
262
            core::MedalError::UnmatchedPasswords => {
Robert Czechowski's avatar
Robert Czechowski committed
263
264
265
266
                IronError { error: Box::new(SessionError { message:
                                                               "The two passwords did not match.".to_string() }),
                            response: Response::with(status::Forbidden) }
            }
267
        }
268
269
270
    }
}

271
272
273
trait RequestAugmentMedalError<'c, 'a: 'c, 'b: 'c + 'a, R> {
    fn aug(self, req: &'c mut Request<'a, 'b>) -> Result<R, AugMedalError<'c, 'a, 'b>>;
}
274
impl<'c, 'a: 'c, 'b: 'c + 'a, T> RequestAugmentMedalError<'c, 'a, 'b, T> for Result<T, core::MedalError> {
275
    fn aug(self, req: &'c mut Request<'a, 'b>) -> Result<T, AugMedalError<'c, 'a, 'b>> {
Robert Czechowski's avatar
Robert Czechowski committed
276
        self.map_err(move |me| AugMedalError(me, req))
277
278
279
    }
}

280
281
fn greet_personal<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
282
    let session_token = req.get_session_token();
Robert Czechowski's avatar
Robert Czechowski committed
283
284
    // hier ggf. Daten aus dem Request holen

285
286
    let config = req.get::<Read<SharedConfiguration>>().unwrap();
    let (self_url, oauth_providers) = (config.self_url.clone(), config.oauth_providers.clone());
287

288
    let (template, mut data) = {
Robert Czechowski's avatar
Robert Czechowski committed
289
        // hier ggf. Daten aus dem Request holen
290
        let mutex = req.get::<Write<SharedDatabaseConnection<C>>>().unwrap();
Robert Czechowski's avatar
Robert Czechowski committed
291
292
        let conn = mutex.lock().unwrap_or_else(|e| e.into_inner());

293
        // Antwort erstellen und zurücksenden
294
        core::index(&*conn, session_token, (self_url, oauth_providers))
Robert Czechowski's avatar
Robert Czechowski committed
295
    };
296

297
298
299
300
301
    /*if let Some(server_message) = &config.server_message {
        data.insert("server_message".to_string(), to_json(&server_message));
    }*/
    config.server_message.as_ref().map(|sm| data.insert("server_message".to_string(), to_json(&sm)));

302
    // Antwort erstellen und zurücksenden
Robert Czechowski's avatar
Robert Czechowski committed
303
304
305
306
307
    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

308
309
310
311
312
313
314
315
316
317
318
319
320
321
fn dbstatus<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
    let status = {
        let mutex = req.get::<Write<SharedDatabaseConnection<C>>>().unwrap();
        let conn = mutex.lock().unwrap_or_else(|e| e.into_inner());

        core::status(&*conn)
    };

    let mut resp = Response::new();
    resp.set_mut(status).set_mut(status::Ok);
    Ok(resp)
}

322
323
fn debug<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
324
325
326
    let session_token = req.get_session_token();

    let (template, data) = {
327
        let mutex = req.get::<Write<SharedDatabaseConnection<C>>>().unwrap();
328
329
        let conn = mutex.lock().unwrap_or_else(|e| e.into_inner());

330
        core::debug(&*conn, session_token)
331
332
333
334
335
336
337
    };

    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

338
339
fn debug_new_token<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
340
341
    let session_token = req.get_session_token();

342
343
    #[cfg(feature = "debug")]
    println!("Logging out session {:?}", session_token);
344

345
    with_conn![core::logout, C, req, session_token];
346
347
348
349

    Ok(Response::with((status::Found, Redirect(url_for!(req, "debug")))))
}

350
351
fn debug_logout<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
352
353
    let session_token = req.get_session_token();

354
355
    #[cfg(feature = "debug")]
    println!("Logging out session {:?}", session_token);
356

357
    with_conn![core::logout, C, req, session_token];
358
359
360
361

    Ok(Response::with((status::Found, Redirect(url_for!(req, "debug")))))
}

362
363
fn debug_create_session<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
364
365
    let session_token = req.get_session_token();

366
    with_conn![core::debug_create_session, C, req, session_token];
367
368
369
370

    Ok(Response::with((status::Found, Redirect(url_for!(req, "debug")))))
}

371
372
fn contests<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
373
    let session_token = req.require_session_token()?;
374
375
376
377
378
379
380
    let query_string = req.url.query().unwrap_or("").to_string();

    // TODO: Move to core::* ?
    let visibility = if query_string.contains("open") {
        core::ContestVisibility::Open
    } else if query_string.contains("current") {
        core::ContestVisibility::Current
381
382
    } else if query_string.contains("challenge") {
        core::ContestVisibility::LoginRequired
383
384
385
    } else {
        core::ContestVisibility::All
    };
386

387
388
    let config = req.get::<Read<SharedConfiguration>>().unwrap();
    let (self_url, oauth_providers) = (config.self_url.clone(), config.oauth_providers.clone());
389

390
391
    let (template, mut data) =
        with_conn![core::show_contests, C, req, &session_token, (self_url, oauth_providers), visibility];
392
393

    config.server_message.as_ref().map(|sm| data.insert("server_message".to_string(), to_json(&sm)));
394

395
396
397
398
    if query_string.contains("results") {
        data.insert("direct_link_to_results".to_string(), to_json(&true));
    }

399
400
401
402
403
404
405
    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

fn opencontests<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
406
    let session_token = req.require_session_token()?;
407

408
409
    let config = req.get::<Read<SharedConfiguration>>().unwrap();
    let (self_url, oauth_providers) = (config.self_url.clone(), config.oauth_providers.clone());
410

411
412
413
414
415
416
417
418
    let (template, mut data) = with_conn![core::show_contests,
                                          C,
                                          req,
                                          &session_token,
                                          (self_url, oauth_providers),
                                          core::ContestVisibility::Open];

    config.server_message.as_ref().map(|sm| data.insert("server_message".to_string(), to_json(&sm)));
419
420
421
422
423
424
425
426

    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

fn currentcontests<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
427
    let session_token = req.require_session_token()?;
428

429
430
    let config = req.get::<Read<SharedConfiguration>>().unwrap();
    let (self_url, oauth_providers) = (config.self_url.clone(), config.oauth_providers.clone());
431

432
433
434
435
436
437
438
439
    let (template, mut data) = with_conn![core::show_contests,
                                          C,
                                          req,
                                          &session_token,
                                          (self_url, oauth_providers),
                                          core::ContestVisibility::Current];

    config.server_message.as_ref().map(|sm| data.insert("server_message".to_string(), to_json(&sm)));
440

Robert Czechowski's avatar
Robert Czechowski committed
441
442
443
444
445
    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

446
447
fn contest<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
448
    let contest_id = req.expect_int::<i32>("contestid")?;
449
    let session_token = req.require_session_token()?;
450
    let query_string = req.url.query().map(|s| s.to_string());
Robert Czechowski's avatar
Robert Czechowski committed
451

452
    let (template, data) = with_conn![core::show_contest, C, req, contest_id, &session_token, query_string].aug(req)?;
Robert Czechowski's avatar
Robert Czechowski committed
453
454
455
456
457
458

    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

459
460
fn contestresults<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
461
462
    let config = req.get::<Read<SharedConfiguration>>().unwrap();
    let disable_contest_results = config.disable_results_page.unwrap_or(false);
463
464
465
466
467
468
469

    if disable_contest_results {
        let mut resp = Response::new();
        resp.set_mut(Template::new(&"nocontestresults", 2)).set_mut(status::Locked);
        return Ok(resp);
    }

470
    let contest_id = req.expect_int::<i32>("contestid")?;
471
472
    let session_token = req.require_session_token()?;

473
    let (template, data) = with_conn![core::show_contest_results, C, req, contest_id, &session_token].aug(req)?;
Daniel Brüning's avatar
Daniel Brüning committed
474

475
476
477
478
479
    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

480
481
482
483
484
485
fn contestresults_download<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
    let config = req.get::<Read<SharedConfiguration>>().unwrap();
    let disable_contest_results = config.disable_results_page.unwrap_or(false);

    println!("test");
486

487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
    if disable_contest_results {
        let mut resp = Response::new();
        resp.set_mut(Template::new(&"nocontestresults", 2)).set_mut(status::Locked);
        return Ok(resp);
    }

    let contest_id = req.expect_int::<i32>("contestid")?;
    let session_token = req.require_session_token()?;

    let (template, data) = with_conn![core::show_contest_results, C, req, contest_id, &session_token].aug(req)?;

    use iron::headers::{Charset, ContentDisposition, DispositionParam, DispositionType};

    let cd = ContentDisposition { disposition: DispositionType::Attachment,
                                  parameters: vec![DispositionParam::Filename(
        Charset::Ext("Utf-8".to_string()), // The character set for the bytes of the filename
        None,                              // The optional language tag (see `language-tag` crate)
        format!("{}.csv", data.get("contestname").unwrap().as_str().unwrap()).as_bytes().to_vec(), // the actual bytes of the filename
505
                                                                                                   // TODO: The name should be returned by core::show_contest_results directly
506
507
508
509
510
511
512
513
    )] };

    let mut resp = Response::new();
    resp.headers.set(cd);
    resp.set_mut(Template::new(&format!("{}_download", template), data)).set_mut(status::Ok);
    Ok(resp)
}

514
515
fn contest_post<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
516
    let contest_id = req.expect_int::<i32>("contestid")?;
517
    let session_token = req.expect_session_token()?;
518

Robert Czechowski's avatar
Robert Czechowski committed
519
520
    let csrf_token = {
        let formdata = itry!(req.get_ref::<UrlEncodedBody>());
521
        iexpect!(formdata.get("csrf_token"))[0].to_owned()
Robert Czechowski's avatar
Robert Czechowski committed
522
523
    };

524
    // TODO: Was mit dem Result?
525
    with_conn![core::start_contest, C, req, contest_id, &session_token, &csrf_token].aug(req)?;
Robert Czechowski's avatar
Robert Czechowski committed
526

527
    Ok(Response::with((status::Found, Redirect(url_for!(req, "contest", "contestid" => format!("{}",contest_id))))))
Robert Czechowski's avatar
Robert Czechowski committed
528
529
}

530
531
fn login<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
532
533
    let config = req.get::<Read<SharedConfiguration>>().unwrap();
    let (self_url, oauth_providers) = (config.self_url.clone(), config.oauth_providers.clone());
534

535
    let mut data = json_val::Map::new();
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550

    let query_string = req.url.query().map(|s| s.to_string());
    if let Some(query) = query_string {
        data.insert("forward".to_string(), to_json(&query));
    }

    let mut oauth_links: Vec<(String, String, String)> = Vec::new();
    if let Some(oauth_providers) = oauth_providers {
        for oauth_provider in oauth_providers {
            oauth_links.push((oauth_provider.provider_id.to_owned(),
                              oauth_provider.login_link_text.to_owned(),
                              oauth_provider.url.to_owned()));
        }
    }

551
    data.insert("self_url".to_string(), to_json(&self_url));
552
    data.insert("oauth_links".to_string(), to_json(&oauth_links));
553
    data.insert("parent".to_string(), to_json(&"base"));
554

Robert Czechowski's avatar
Robert Czechowski committed
555
    let mut resp = Response::new();
556
    resp.set_mut(Template::new("login", data)).set_mut(status::Ok);
Robert Czechowski's avatar
Robert Czechowski committed
557
558
559
    Ok(resp)
}

560
561
fn login_post<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
Robert Czechowski's avatar
Robert Czechowski committed
562
563
    let logindata = {
        let formdata = itry!(req.get_ref::<UrlEncodedBody>());
Robert Czechowski's avatar
Robert Czechowski committed
564
        (iexpect!(formdata.get("username"))[0].to_owned(), iexpect!(formdata.get("password"))[0].to_owned())
Robert Czechowski's avatar
Robert Czechowski committed
565
566
    };

567
568
    let config = req.get::<Read<SharedConfiguration>>().unwrap();
    let (self_url, oauth_providers) = (config.self_url.clone(), config.oauth_providers.clone());
569

570
    // TODO: Submit current session to login
Daniel Brüning's avatar
Daniel Brüning committed
571

572
    let loginresult = with_conn![core::login, C, req, logindata, (self_url, oauth_providers)];
Robert Czechowski's avatar
Robert Czechowski committed
573
574
575
576
577
578

    match loginresult {
        // Login successful
        Ok(sessionkey) => {
            req.session().set(SessionToken { token: sessionkey }).unwrap();
            Ok(Response::with((status::Found, Redirect(url_for!(req, "greet")))))
Robert Czechowski's avatar
Robert Czechowski committed
579
        }
Robert Czechowski's avatar
Robert Czechowski committed
580
581
582
583
584
585
586
587
588
        // Login failed
        Err((template, data)) => {
            let mut resp = Response::new();
            resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
            Ok(resp)
        }
    }
}

589
590
fn login_code_post<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
Robert Czechowski's avatar
Robert Czechowski committed
591
592
593
594
595
    let code = {
        let formdata = itry!(req.get_ref::<UrlEncodedBody>());
        iexpect!(formdata.get("code"))[0].to_owned()
    };

596
597
    let config = req.get::<Read<SharedConfiguration>>().unwrap();
    let (self_url, oauth_providers) = (config.self_url.clone(), config.oauth_providers.clone());
598

599
600
    // TODO: Submit current session to login

601
    let loginresult = with_conn![core::login_with_code, C, req, &code, (self_url, oauth_providers)];
Robert Czechowski's avatar
Robert Czechowski committed
602
603
604
605
606
607

    match loginresult {
        // Login successful
        Ok(Ok(sessionkey)) => {
            req.session().set(SessionToken { token: sessionkey }).unwrap();
            Ok(Response::with((status::Found, Redirect(url_for!(req, "greet")))))
Robert Czechowski's avatar
Robert Czechowski committed
608
        }
Robert Czechowski's avatar
Robert Czechowski committed
609
610
        Ok(Err(sessionkey)) => {
            req.session().set(SessionToken { token: sessionkey }).unwrap();
611
612
            //Ok(Response::with((status::Found, Redirect(url_for!(req, "profile")))))
            Ok(Response::with((status::Found,
613
614
                               Redirect(iron::Url::parse(&format!("{}?status=firstlogin",
                                                                  &url_for!(req, "profile"))).unwrap()))))
Robert Czechowski's avatar
Robert Czechowski committed
615
        }
Robert Czechowski's avatar
Robert Czechowski committed
616
617
618
619
620
621
622
        // Login failed
        Err((template, data)) => {
            let mut resp = Response::new();
            resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
            Ok(resp)
        }
    }
623
}
Robert Czechowski's avatar
Robert Czechowski committed
624

625
626
fn logout<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
627
    let session_token = req.get_session_token();
Robert Czechowski's avatar
Robert Czechowski committed
628

629
    println!("Loggin out session {:?}", session_token);
Daniel Brüning's avatar
Daniel Brüning committed
630

631
    with_conn![core::logout, C, req, session_token];
Robert Czechowski's avatar
Robert Czechowski committed
632
633
634
635

    Ok(Response::with((status::Found, Redirect(url_for!(req, "greet")))))
}

636
637
fn submission<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
638
    let task_id = req.expect_int::<i32>("taskid")?;
639
    let session_token = req.expect_session_token()?;
Robert Czechowski's avatar
Robert Czechowski committed
640
641
642
    let subtask: Option<String> = (|| -> Option<String> {
        req.get_ref::<UrlEncodedQuery>().ok()?.get("subtask")?.get(0).map(|x| x.to_owned())
    })();
643

644
    let result = with_conn![core::load_submission, C, req, task_id, &session_token, subtask];
Robert Czechowski's avatar
Robert Czechowski committed
645
646

    match result {
Robert Czechowski's avatar
Robert Czechowski committed
647
648
        Ok(data) => Ok(Response::with((status::Ok, mime!(Application / Json), format!("{}", data)))),
        Err(_) => Ok(Response::with((status::BadRequest, mime!(Application / Json), format!("{{}}")))),
Robert Czechowski's avatar
Robert Czechowski committed
649
650
651
    }
}

652
653
fn submission_post<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
654
    let task_id = req.expect_int::<i32>("taskid")?;
655
    let session_token = req.expect_session_token()?;
656
    let (csrf_token, data, grade, subtask) = {
Robert Czechowski's avatar
Robert Czechowski committed
657
        let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
658
659
660
661
        (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
         iexpect!(formdata.get("data"))[0].to_owned(),
         iexpect!(formdata.get("grade").unwrap_or(&vec!["0".to_owned()])[0].parse::<i32>().ok()),
         formdata.get("subtask").map(|x| x[0].to_owned()))
Robert Czechowski's avatar
Robert Czechowski committed
662
    };
663
664
665

    #[cfg(feature = "debug")]
    println!("New submission for task {} (graded {}): {}", task_id, grade, data);
666

667
    let result =
668
        with_conn![core::save_submission, C, req, task_id, &session_token, &csrf_token, data, grade, subtask].aug(req)?;
Daniel Brüning's avatar
Daniel Brüning committed
669

670
    Ok(Response::with((status::Ok, mime!(Application / Json), result)))
Robert Czechowski's avatar
Robert Czechowski committed
671
672
}

673
674
fn task<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
675
    let task_id = req.expect_int::<i32>("taskid")?;
676
    let session_token = req.require_session_token()?;
677

678
    let (template, data) = with_conn![core::show_task, C, req, task_id, &session_token].aug(req)?;
Robert Czechowski's avatar
Robert Czechowski committed
679
680
681
682
683
684

    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

685
686
fn groups<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
687
    let session_token = req.require_session_token()?;
Daniel Brüning's avatar
Daniel Brüning committed
688

689
    let (template, data) = with_conn![core::show_groups, C, req, &session_token].aug(req)?;
Daniel Brüning's avatar
Daniel Brüning committed
690

Robert Czechowski's avatar
Robert Czechowski committed
691
692
693
694
695
    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

696
697
fn group<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
698
    let group_id = req.expect_int::<i32>("groupid")?;
699
    let session_token = req.require_session_token()?;
Daniel Brüning's avatar
Daniel Brüning committed
700

701
    let (template, data) = with_conn![core::show_group, C, req, group_id, &session_token].aug(req)?;
Daniel Brüning's avatar
Daniel Brüning committed
702

703
704
705
    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
Robert Czechowski's avatar
Robert Czechowski committed
706
707
}

708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
fn group_download<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
    let group_id = req.expect_int::<i32>("groupid")?;
    let session_token = req.require_session_token()?;

    let (template, data) = with_conn![core::show_group, C, req, group_id, &session_token].aug(req)?;

    use iron::headers::{Charset, ContentDisposition, DispositionParam, DispositionType};

    let cd = ContentDisposition { disposition: DispositionType::Attachment,
                                  parameters: vec![DispositionParam::Filename(
        Charset::Ext("Utf-8".to_string()), // The character set for the bytes of the filename
        None,                              // The optional language tag (see `language-tag` crate)
        format!("{}.csv", data.get("groupname").unwrap().as_str().unwrap()).as_bytes().to_vec(), // the actual bytes of the filename
                                                                                                 // TODO: The name should be returned by core::show_group directly
    )] };

    let mut resp = Response::new();
    resp.headers.set(cd);
    resp.set_mut(Template::new(&format!("{}_download", template), data)).set_mut(status::Ok);
    Ok(resp)
}

731
//TODO: Secure with CSRF-Token?
732
733
fn group_post<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
734
    let group_id = req.expect_int::<i32>("groupid")?;
735
    let session_token = req.expect_session_token()?;
Robert Czechowski's avatar
Robert Czechowski committed
736

737
    //TODO: use result?
738
    with_conn![core::modify_group, C, req, group_id, &session_token].aug(req)?;
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
739

740
    Ok(Response::with((status::Found, Redirect(url_for!(req, "group", "groupid" => format!("{}",group_id))))))
Robert Czechowski's avatar
Robert Czechowski committed
741
742
}

743
744
fn new_group<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
745
    let session_token = req.require_session_token()?;
Robert Czechowski's avatar
Robert Czechowski committed
746

747
    let (csrf_token, name, tag) = {
Robert Czechowski's avatar
Robert Czechowski committed
748
        let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
749
750
751
        (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
         iexpect!(formdata.get("name"))[0].to_owned(),
         iexpect!(formdata.get("tag"))[0].to_owned())
Robert Czechowski's avatar
Robert Czechowski committed
752
753
    };

754
    let group_id = with_conn![core::add_group, C, req, &session_token, &csrf_token, name, tag].aug(req)?;
Daniel Brüning's avatar
Daniel Brüning committed
755

756
    Ok(Response::with((status::Found, Redirect(url_for!(req, "group", "groupid" => format!("{}",group_id))))))
Robert Czechowski's avatar
Robert Czechowski committed
757
758
}

759
760
761
762
763
764
765
766
767
768
769
770
771
fn group_csv<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
    let session_token = req.require_session_token()?;

    template_ok!(with_conn![core::group_csv, C, req, &session_token].aug(req)?)
}

fn group_csv_upload<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
    let session_token = req.require_session_token()?;

    let (csrf_token, group_data) = {
        let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
772
        (iexpect!(formdata.get("csrf_token"))[0].to_owned(), iexpect!(formdata.get("group_data"))[0].to_owned())
773
774
    };

Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
775
776
    println!("{}", group_data);

777
    with_conn![core::upload_groups, C, req, &session_token, &csrf_token, &group_data].aug(req)?;
778
779
780
781

    Ok(Response::with((status::Found, Redirect(url_for!(req, "groups")))))
}

782
783
fn profile<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
784
    let session_token = req.require_session_token()?;
Robert Czechowski's avatar
Robert Czechowski committed
785
    let query_string = req.url.query().map(|s| s.to_string());
Daniel Brüning's avatar
Daniel Brüning committed
786

787
    let (template, data) = with_conn![core::show_profile, C, req, &session_token, None, query_string].aug(req)?;
Daniel Brüning's avatar
Daniel Brüning committed
788

Robert Czechowski's avatar
Robert Czechowski committed
789
790
791
792
793
    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

794
795
fn profile_post<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
796
    let session_token = req.expect_session_token()?;
797
    let (csrf_token, firstname, lastname, street, zip, city, pwd, pwd_repeat, grade, sex) = {
Robert Czechowski's avatar
Robert Czechowski committed
798
        let formdata = itry!(req.get_ref::<UrlEncodedBody>());
799
        (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
Robert Czechowski's avatar
Robert Czechowski committed
800
801
         iexpect!(formdata.get("firstname"))[0].to_owned(),
         iexpect!(formdata.get("lastname"))[0].to_owned(),
802
803
804
805
806
         formdata.get("street").map(|x| x[0].to_owned()),
         formdata.get("zip").map(|x| x[0].to_owned()),
         formdata.get("city").map(|x| x[0].to_owned()),
         formdata.get("password").map(|x| x[0].to_owned()),
         formdata.get("password_repeat").map(|x| x[0].to_owned()),
807
808
         iexpect!(formdata.get("grade"))[0].parse::<i32>().unwrap_or(0),
         iexpect!(formdata.get("sex"))[0].parse::<i32>().ok())
Robert Czechowski's avatar
Robert Czechowski committed
809
    };
Daniel Brüning's avatar
Daniel Brüning committed
810

811
812
813
814
815
816
817
818
    let profilechangeresult =
        with_conn![core::edit_profile,
                   C,
                   req,
                   &session_token,
                   None,
                   &csrf_token,
                   (firstname, lastname, street, zip, city, pwd, pwd_repeat, grade, sex)].aug(req)?;
819
820
821
822
823

    Ok(Response::with((status::Found,
                       Redirect(iron::Url::parse(&format!("{}?status={:?}",
                                                          &url_for!(req, "profile"),
                                                          profilechangeresult)).unwrap()))))
Robert Czechowski's avatar
Robert Czechowski committed
824
825
}

826
827
fn user<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
828
    let user_id = req.expect_int::<i32>("userid")?;
829
    let session_token = req.expect_session_token()?;
Robert Czechowski's avatar
Robert Czechowski committed
830
    let query_string = req.url.query().map(|s| s.to_string());
Daniel Brüning's avatar
Daniel Brüning committed
831

Robert Czechowski's avatar
Robert Czechowski committed
832
    let (template, data) =
833
        with_conn![core::show_profile, C, req, &session_token, Some(user_id), query_string].aug(req)?;
Daniel Brüning's avatar
Daniel Brüning committed
834

Robert Czechowski's avatar
Robert Czechowski committed
835
836
837
838
839
    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

840
841
fn user_post<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
842
    let user_id = req.expect_int::<i32>("userid")?;
843
    let session_token = req.expect_session_token()?;
844
    let (csrf_token, firstname, lastname, street, zip, city, pwd, pwd_repeat, grade, sex) = {
845
        let formdata = itry!(req.get_ref::<UrlEncodedBody>());
846
        (iexpect!(formdata.get("csrf_token"))[0].to_owned(),
Robert Czechowski's avatar
Robert Czechowski committed
847
848
         iexpect!(formdata.get("firstname"))[0].to_owned(),
         iexpect!(formdata.get("lastname"))[0].to_owned(),
849
850
851
852
853
         formdata.get("street").map(|x| x[0].to_owned()),
         formdata.get("zip").map(|x| x[0].to_owned()),
         formdata.get("city").map(|x| x[0].to_owned()),
         formdata.get("password").map(|x| x[0].to_owned()),
         formdata.get("password_repeat").map(|x| x[0].to_owned()),
854
855
         iexpect!(formdata.get("grade"))[0].parse::<i32>().unwrap_or(0),
         iexpect!(formdata.get("sex"))[0].parse::<i32>().ok())
856
    };
Daniel Brüning's avatar
Daniel Brüning committed
857

858
859
860
861
862
863
864
865
    let profilechangeresult =
        with_conn![core::edit_profile,
                   C,
                   req,
                   &session_token,
                   Some(user_id),
                   &csrf_token,
                   (firstname, lastname, street, zip, city, pwd, pwd_repeat, grade, sex)].aug(req)?;
866
867
868
869
870
871

    Ok(Response::with((status::Found,
                       Redirect(iron::Url::parse(&format!("{}?status={:?}",
                                                          &url_for!(req, "user", "userid" => format!("{}",user_id)),
                                                          profilechangeresult)).unwrap()))))
    //old:   Ok(Response::with((status::Found, Redirect(url_for!(req, "user", "userid" => format!("{}",user_id))))))
872
873
}

874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
fn admin<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
    let session_token = req.expect_session_token()?;

    let mut data = json_val::Map::new();
    let mut resp = Response::new();
    resp.set_mut(Template::new("admin", data)).set_mut(status::Ok);
    Ok(resp)
}

fn admin_users<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
    let session_token = req.expect_session_token()?;

    let (s_id, s_firstname, s_lastname, s_logincode, s_pms_id) = {
        let formdata = itry!(req.get_ref::<UrlEncodedBody>());
        (formdata.get("id").map(|x| x[0].parse::<i32>().unwrap_or(0)),
         formdata.get("firstname").map(|x| x[0].to_owned()),
         formdata.get("lastname").map(|x| x[0].to_owned()),
         formdata.get("logincode").map(|x| x[0].to_owned()),
         formdata.get("pmsid").map(|x| x[0].to_owned()))
    };

    let (template, data) = with_conn![core::admin_search_users,
                                      C,
                                      req,
                                      &session_token,
                                      (s_id, s_firstname, s_lastname, s_logincode, s_pms_id)].aug(req)?;

    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

fn admin_user<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
    let user_id = req.expect_int::<i32>("userid")?;
    let session_token = req.expect_session_token()?;

    let csrf_token = if let Ok(formdata) = req.get_ref::<UrlEncodedBody>() {
        // or iexpect!(formdata.get("csrf_token"))[0].to_owned(), ?
        formdata.get("csrf_token").map(|x| x[0].to_owned())
    } else {
        None
    };

    let (template, data) = if let Some(csrf_token) = csrf_token {
        with_conn![core::admin_delete_user, C, req, user_id, &session_token, &csrf_token].aug(req)?
    } else {
        with_conn![core::admin_show_user, C, req, user_id, &session_token].aug(req)?
    };

    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

fn admin_group<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
933
    let group_id = req.expect_int::<i32>("groupid")?;
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
    let session_token = req.expect_session_token()?;

    let csrf_token = if let Ok(formdata) = req.get_ref::<UrlEncodedBody>() {
        formdata.get("csrf_token").map(|x| x[0].to_owned())
    } else {
        None
    };

    let (template, data) = if let Some(csrf_token) = csrf_token {
        with_conn![core::admin_delete_group, C, req, group_id, &session_token, &csrf_token].aug(req)?
    } else {
        with_conn![core::admin_show_group, C, req, group_id, &session_token].aug(req)?
    };

    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

fn admin_participation<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
955
    let group_id = req.expect_int::<i32>("participationid")?;
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
    let session_token = req.expect_session_token()?;

    let csrf_token = if let Ok(formdata) = req.get_ref::<UrlEncodedBody>() {
        formdata.get("csrf_token").map(|x| x[0].to_owned())
    } else {
        None
    };

    let (template, data) = if let Some(csrf_token) = csrf_token {
        with_conn![core::admin_delete_participation, C, req, group_id, &session_token, &csrf_token].aug(req)?
    } else {
        with_conn![core::admin_show_participation, C, req, group_id, &session_token].aug(req)?
    };

    let mut resp = Response::new();
    resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
    Ok(resp)
}

975
976
#[derive(Deserialize, Debug)]
struct OAuthAccess {
Robert Czechowski's avatar
Robert Czechowski committed
977
978
    access_token: String,
    token_type: String,
979
    refresh_token: String,
980
981
    expires: Option<i32>,    // documented as 'expires_in'
    expires_in: Option<i32>, // sent as 'expires'
982
983
984
}

#[derive(Deserialize, Debug)]
985
#[allow(non_snake_case)]
986
pub struct OAuthUserData {
Robert Czechowski's avatar
Robert Czechowski committed
987
988
989
990
991
992
    userID: Option<String>, // documented as 'userId'
    userId: Option<String>, // sent as 'userID'
    userType: String,
    gender: String,
    firstName: String,
    lastName: String,
993
    dateOfBirth: Option<String>,
Robert Czechowski's avatar
Robert Czechowski committed
994
    eMail: Option<String>,
995
    userId_int: Option<String>,
996
997
}

998
999
fn oauth<C>(req: &mut Request) -> IronResult<Response>
    where C: MedalConnection + std::marker::Send + 'static {
1000
    use params::{Params, Value};
For faster browsing, not all history is shown. View entire blame