-
Notifications
You must be signed in to change notification settings - Fork 14
/
svc_account.rs
105 lines (89 loc) · 4.18 KB
/
svc_account.rs
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
use tame_oauth::gcp::*;
// This example shows the basics for creating a GCP service account token
// provider and requesting a token from it. This particular example uses the
// reqwest HTTP client, but the point of this crate is that you can use
// whichever one you like as long as you don't mind doing a little bit of
// boilerplate to convert between from http::Request and to http::Response
#[tokio::main]
async fn main() {
let mut args = std::env::args().skip(1);
let key_path = args
.next()
.expect("expected path to a service account json file");
let scopes: Vec<_> = args.collect();
let service_key = std::fs::read_to_string(key_path).expect("failed to read json key");
// Deserialize the service account info from the json data
let acct_info = ServiceAccountInfo::deserialize(service_key).unwrap();
// Create the token provider!
let sa_provider = ServiceAccountProvider::new(acct_info).unwrap();
// Attempt to get a token, since we have never used this accessor
// before, it's guaranteed that we will need to make an HTTPS
// request to the token provider to retrieve a token. This
// will also happen if we want to get a token for a different set
// of scopes, or if the token has expired.
let token = match sa_provider.get_token(&scopes).unwrap() {
TokenOrRequest::Request {
// This is an http::Request that we can use to build
// a client request for whichever HTTP client implementation
// you wish to use
request,
scope_hash,
..
} => {
let client = reqwest::Client::new();
let (parts, body) = request.into_parts();
let uri = parts.uri.to_string();
// This will always be a POST, but for completeness sake...
let builder = match parts.method {
http::Method::GET => client.get(&uri),
http::Method::POST => client.post(&uri),
http::Method::DELETE => client.delete(&uri),
http::Method::PUT => client.put(&uri),
method => unimplemented!("{} not implemented", method),
};
// Build the full request from the headers and body that were
// passed to you, without modifying them.
let request = builder.headers(parts.headers).body(body).build().unwrap();
// Send the actual request
let response = client.execute(request).await.unwrap();
let mut builder = http::Response::builder()
.status(response.status())
.version(response.version());
let headers = builder.headers_mut().unwrap();
// Unfortunately http doesn't expose a way to just use
// an existing HeaderMap, so we have to copy them :(
headers.extend(
response
.headers()
.into_iter()
.map(|(k, v)| (k.clone(), v.clone())),
);
let buffer = response.bytes().await.unwrap();
let response = builder.body(buffer).unwrap();
// Tell our accessor about the response, also passing
// the scope_hash for the scopes we initially requested,
// this will allow future token requests for those scopes
// to use a cached token, at least until it expires (~1 hour)
sa_provider
.parse_token_response(scope_hash, response)
.unwrap()
}
_ => unreachable!(),
};
// Uncomment this if you want to go to lunch and see an unreachable panic
// when you get back
// std::thread::sleep(std::time::Duration::from_secs(60 * 60))
// Retrieving a token for the same scopes for which a token has been acquired
// will use the cached token until it expires
match sa_provider.get_token(&scopes).unwrap() {
TokenOrRequest::Token(tk) => {
assert_eq!(tk, token);
println!(
"cool, you were able to retrieve a token for the {:?} scope{}!",
scopes,
if scopes.len() == 1 { "" } else { "s" }
);
}
_ => unreachable!(),
}
}