1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
//! A hyper server that authenticates an user initially and then keeps the auth credentials
//! as part of a "session" through a cookie and in-memory store
//! can be adopted to database-based session management
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use hyper::{Body, Request, Response, Server};
use hyper::service::{make_service_fn, service_fn_ok};
use hyper::rt::{self, Future};
use winauth::http::Authenticator;
use cfg_if::cfg_if;
cfg_if! {
if #[cfg(windows)] {
fn main() {
let addr = ([127, 0, 0, 1], 3000).into();
// Stores a cookie <-> username mapping
// FIXME Could be a Database or if there shouldnt be storage used for sessions
// JWTs, which do not require a store but only a cookie that is signed.
let dummy_session_store: Arc<Mutex<HashMap<String, String>>> = Arc::new(Mutex::new(HashMap::new()));
let server = Server::bind(&addr)
.http1_only(true) // winauth only works for HTTP1
.serve(make_service_fn(move |_socket: &hyper::server::conn::AddrStream| {
let dummy_session_store = dummy_session_store.clone();
let mut auth = AuthContext::None;
service_fn_ok(move |req: Request<Body>| {
// Handle requests that work with sessions or might not need auth first
if !req.uri().path().starts_with("/auth/windows") {
// FIXME The cookie handling here is very minimal for demo purposes. This wont work in production!
let content = match req.headers().get("cookie").map(|x| x.to_str().unwrap()) {
Some(ref val) => {
let store = dummy_session_store.lock().unwrap();
let user = store.get(val.split("=").nth(1).unwrap()).unwrap();
format!("Welcome Back, {}", user)
}
None => "Not logged in. Go to /auth/windows".to_owned(),
};
let mut builder = hyper::Response::builder();
return builder.body(content.into()).unwrap();
}
// Perform authentication (at most once per connection) for Windows
if let AuthContext::None = auth {
let sspi = winauth::windows::NtlmSspiBuilder::new()
.inbound()
.build()
.unwrap();
auth = AuthContext::Pending(sspi);
}
if let AuthContext::Pending(ref mut sspi) = auth {
let fetch_header = |header_name| Ok(
req.headers().get(header_name).map(|x| x.to_str()).transpose()?
);
auth = match sspi.http_incoming_auth(fetch_header).unwrap() {
// This cannot happen for the server-side
winauth::http::AuthState::NotRequested => unreachable!(),
// Pass along a http-response to the client, if needed for auth.
// The client will retry the request and include additional auth data.
winauth::http::AuthState::Response(ref resp) => {
let mut builder = hyper::Response::builder();
for (k, v) in &resp.headers {
builder.header(*k, v);
}
builder.status(resp.status_code);
return builder.body("".into()).unwrap();
},
// We finally have performed enough requests and authenticated successfully
// Get the username into the client name
winauth::http::AuthState::Success => AuthContext::Authenticated(sspi.client_identity().unwrap()),
};
}
// Authentication was successful. Store the username in a dummy session store (as we would in a database)
let username = auth.username();
let cookie_name = generate_INSECURE_random_string();
println!("Got auth request from user {}. Setting cookie {}", username, cookie_name);
dummy_session_store.lock().unwrap().insert(cookie_name.clone(), username.to_owned());
Response::builder()
.header("Set-Cookie", format!("session={};path=/", cookie_name)) // FIXME Use the cookie crate for this in production!
.body(Body::from(format!("Hello {}", username)))
.unwrap()
})
}))
.map_err(|e| eprintln!("server error: {}", e));
println!("Listening on http://{}", addr);
rt::run(server);
}
/// State of windows authentication for the currently served HTTP connection
/// Ensures auth is performed at most once per connection.
enum AuthContext {
/// We have not yet needed to authenticate for this connection
None,
/// The authentication process for this connection is pending
Pending(winauth::windows::NtlmSspi),
/// We are successfully authenticated as (username).
Authenticated(String),
}
impl AuthContext {
/// Return the username associated with this context, or panic.
fn username(&self) -> &str {
match *self {
AuthContext::None | AuthContext::Pending(_) => panic!("This should not be reachable"),
AuthContext::Authenticated(ref user) => &user,
}
}
}
// FIXME This is just for demonstration purposes.
// Use a cryptographically secure PRNG (e.g. rand's StdRng)!
fn generate_INSECURE_random_string() -> String {
println!("WARNING: DO NOT USE THIS DUMMY COOKIE GENERATOR IN PRODUCTION");
"abcdefghHIGHTLYsecureCOOKIE".to_owned()
}
} // WINDOWS
else {
fn main() {
panic!("only windows supported");
}
}
}