-
-
Notifications
You must be signed in to change notification settings - Fork 141
Expand file tree
/
Copy pathgoogle.rs
More file actions
254 lines (226 loc) · 8.95 KB
/
google.rs
File metadata and controls
254 lines (226 loc) · 8.95 KB
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
//!
//! This example showcases the process of integrating with the
//! [Google OpenID Connect](https://developers.google.com/identity/protocols/OpenIDConnect)
//! provider.
//!
//! Before running it, you'll need to generate your own Google OAuth2 credentials.
//!
//! In order to run the example call:
//!
//! ```sh
//! GOOGLE_CLIENT_ID=xxx GOOGLE_CLIENT_SECRET=yyy cargo run --example google
//! ```
//!
//! ...and follow the instructions.
//!
use openidconnect::core::{
CoreAuthDisplay, CoreClaimName, CoreClaimType, CoreClient, CoreClientAuthMethod, CoreGrantType,
CoreIdTokenClaims, CoreIdTokenVerifier, CoreJsonWebKey, CoreJsonWebKeyType, CoreJsonWebKeyUse,
CoreJweContentEncryptionAlgorithm, CoreJweKeyManagementAlgorithm, CoreJwsSigningAlgorithm,
CoreResponseMode, CoreResponseType, CoreRevocableToken, CoreSubjectIdentifierType,
};
use openidconnect::reqwest;
use openidconnect::{
AdditionalProviderMetadata, AuthenticationFlow, AuthorizationCode, ClientId, ClientSecret,
CsrfToken, IssuerUrl, Nonce, OAuth2TokenResponse, ProviderMetadata, RedirectUrl, RevocationUrl,
Scope,
};
use serde::{Deserialize, Serialize};
use url::Url;
use std::env;
use std::io::{BufRead, BufReader, Write};
use std::net::TcpListener;
use std::process::exit;
fn handle_error<T: std::error::Error>(fail: &T, msg: &'static str) {
let mut err_msg = format!("ERROR: {}", msg);
let mut cur_fail: Option<&dyn std::error::Error> = Some(fail);
while let Some(cause) = cur_fail {
err_msg += &format!("\n caused by: {}", cause);
cur_fail = cause.source();
}
println!("{}", err_msg);
exit(1);
}
// Teach openidconnect-rs about a Google custom extension to the OpenID Discovery response that we can use as the RFC
// 7009 OAuth 2.0 Token Revocation endpoint. For more information about the Google specific Discovery response see the
// Google OpenID Connect service documentation at: https://developers.google.com/identity/protocols/oauth2/openid-connect#discovery
#[derive(Clone, Debug, Deserialize, Serialize)]
struct RevocationEndpointProviderMetadata {
revocation_endpoint: String,
}
impl AdditionalProviderMetadata for RevocationEndpointProviderMetadata {}
type GoogleProviderMetadata = ProviderMetadata<
RevocationEndpointProviderMetadata,
CoreAuthDisplay,
CoreClientAuthMethod,
CoreClaimName,
CoreClaimType,
CoreGrantType,
CoreJweContentEncryptionAlgorithm,
CoreJweKeyManagementAlgorithm,
CoreJwsSigningAlgorithm,
CoreJsonWebKeyType,
CoreJsonWebKeyUse,
CoreJsonWebKey,
CoreResponseMode,
CoreResponseType,
CoreSubjectIdentifierType,
>;
fn main() {
env_logger::init();
let google_client_id = ClientId::new(
env::var("GOOGLE_CLIENT_ID").expect("Missing the GOOGLE_CLIENT_ID environment variable."),
);
let google_client_secret = ClientSecret::new(
env::var("GOOGLE_CLIENT_SECRET")
.expect("Missing the GOOGLE_CLIENT_SECRET environment variable."),
);
let issuer_url =
IssuerUrl::new("https://accounts.google.com".to_string()).unwrap_or_else(|err| {
handle_error(&err, "Invalid issuer URL");
unreachable!();
});
let http_client = reqwest::blocking::ClientBuilder::new()
// Following redirects opens the client up to SSRF vulnerabilities.
.redirect(reqwest::redirect::Policy::none())
.build()
.unwrap_or_else(|err| {
handle_error(&err, "Failed to build HTTP client");
unreachable!();
});
// Fetch Google's OpenID Connect discovery document.
//
// Note: If we don't care about token revocation we can simply use CoreProviderMetadata here
// instead of GoogleProviderMetadata. If instead we wanted to optionally use the token
// revocation endpoint if it seems to be supported we could do something like this:
// #[derive(Clone, Debug, Deserialize, Serialize)]
// struct AllOtherProviderMetadata(HashMap<String, serde_json::Value>);
// impl AdditionalClaims for AllOtherProviderMetadata {}
// And then test for the presence of "revocation_endpoint" in the map returned by a call to
// .additional_metadata().
let provider_metadata = GoogleProviderMetadata::discover(&issuer_url, &http_client)
.unwrap_or_else(|err| {
handle_error(&err, "Failed to discover OpenID Provider");
unreachable!();
});
let revocation_endpoint = provider_metadata
.additional_metadata()
.revocation_endpoint
.clone();
println!(
"Discovered Google revocation endpoint: {}",
revocation_endpoint
);
// Set up the config for the Google OAuth2 process.
let client = CoreClient::from_provider_metadata(
provider_metadata,
google_client_id,
Some(google_client_secret),
)
// This example will be running its own server at localhost:8080.
// See below for the server implementation.
.set_redirect_uri(
RedirectUrl::new("http://localhost:8080".to_string()).unwrap_or_else(|err| {
handle_error(&err, "Invalid redirect URL");
unreachable!();
}),
)
// Google supports OAuth 2.0 Token Revocation (RFC-7009)
.set_revocation_url(
RevocationUrl::new(revocation_endpoint).unwrap_or_else(|err| {
handle_error(&err, "Invalid revocation endpoint URL");
unreachable!();
}),
);
// Generate the authorization URL to which we'll redirect the user.
let (authorize_url, csrf_state, nonce) = client
.authorize_url(
AuthenticationFlow::<CoreResponseType>::AuthorizationCode,
CsrfToken::new_random,
Nonce::new_random,
)
// This example is requesting access to the "calendar" features and the user's profile.
.add_scope(Scope::new("email".to_string()))
.add_scope(Scope::new("profile".to_string()))
.url();
println!("Open this URL in your browser:\n{}\n", authorize_url);
let (code, state) = {
// A very naive implementation of the redirect server.
let listener = TcpListener::bind("127.0.0.1:8080").unwrap();
// Accept one connection
let (mut stream, _) = listener.accept().unwrap();
let mut reader = BufReader::new(&stream);
let mut request_line = String::new();
reader.read_line(&mut request_line).unwrap();
let redirect_url = request_line.split_whitespace().nth(1).unwrap();
let url = Url::parse(&("http://localhost".to_string() + redirect_url)).unwrap();
let code = url
.query_pairs()
.find(|(key, _)| key == "code")
.map(|(_, code)| AuthorizationCode::new(code.into_owned()))
.unwrap();
let state = url
.query_pairs()
.find(|(key, _)| key == "state")
.map(|(_, state)| CsrfToken::new(state.into_owned()))
.unwrap();
let message = "Go back to your terminal :)";
let response = format!(
"HTTP/1.1 200 OK\r\ncontent-length: {}\r\n\r\n{}",
message.len(),
message
);
stream.write_all(response.as_bytes()).unwrap();
(code, state)
};
println!("Google returned the following code:\n{}\n", code.secret());
println!(
"Google returned the following state:\n{} (expected `{}`)\n",
state.secret(),
csrf_state.secret()
);
// Exchange the code with a token.
let token_response = client
.exchange_code(code)
.unwrap_or_else(|err| {
handle_error(&err, "No user info endpoint");
unreachable!();
})
.request(&http_client)
.unwrap_or_else(|err| {
handle_error(&err, "Failed to contact token endpoint");
unreachable!();
});
println!(
"Google returned access token:\n{}\n",
token_response.access_token().secret()
);
println!("Google returned scopes: {:?}", token_response.scopes());
let id_token_verifier: CoreIdTokenVerifier = client.id_token_verifier();
let id_token_claims: &CoreIdTokenClaims = token_response
.extra_fields()
.id_token()
.expect("Server did not return an ID token")
.claims(&id_token_verifier, &nonce)
.unwrap_or_else(|err| {
handle_error(&err, "Failed to verify ID token");
unreachable!();
});
println!("Google returned ID token: {:?}", id_token_claims);
// Revoke the obtained token
let token_to_revoke: CoreRevocableToken = match token_response.refresh_token() {
Some(token) => token.into(),
None => token_response.access_token().into(),
};
client
.revoke_token(token_to_revoke)
.unwrap_or_else(|err| {
handle_error(&err, "Failed to revoke token");
unreachable!();
})
.request(&http_client)
.unwrap_or_else(|err| {
handle_error(&err, "Failed to revoke token");
unreachable!();
});
}