@@ -18,93 +18,136 @@ extern crate oauth2;
18
18
extern crate rand;
19
19
extern crate url;
20
20
21
+ use oauth2:: prelude:: * ;
22
+ use oauth2:: {
23
+ AuthorizationCode ,
24
+ AuthUrl ,
25
+ ClientId ,
26
+ ClientSecret ,
27
+ CsrfToken ,
28
+ RedirectUrl ,
29
+ Scope ,
30
+ Token ,
31
+ TokenUrl ,
32
+ } ;
21
33
use oauth2:: basic:: BasicClient ;
22
- use rand:: { thread_rng, Rng } ;
23
34
use std:: env;
24
35
use std:: net:: TcpListener ;
25
36
use std:: io:: { BufRead , BufReader , Write } ;
26
37
use url:: Url ;
27
38
28
39
fn main ( ) {
29
- let github_client_id = env:: var ( "GITHUB_CLIENT_ID" ) . expect ( "Missing the GITHUB_CLIENT_ID environment variable." ) ;
30
- let github_client_secret = env:: var ( "GITHUB_CLIENT_SECRET" ) . expect ( "Missing the GITHUB_CLIENT_SECRET environment variable." ) ;
31
- let auth_url = "https://github.com/login/oauth/authorize" ;
32
- let token_url = "https://github.com/login/oauth/access_token" ;
40
+ let github_client_id =
41
+ ClientId :: new (
42
+ env:: var ( "GITHUB_CLIENT_ID" )
43
+ . expect ( "Missing the GITHUB_CLIENT_ID environment variable." )
44
+ ) ;
45
+ let github_client_secret =
46
+ ClientSecret :: new (
47
+ env:: var ( "GITHUB_CLIENT_SECRET" )
48
+ . expect ( "Missing the GITHUB_CLIENT_SECRET environment variable." )
49
+ ) ;
50
+ let auth_url =
51
+ AuthUrl :: new (
52
+ Url :: parse ( "https://github.com/login/oauth/authorize" )
53
+ . expect ( "Invalid authorization endpoint URL" )
54
+ ) ;
55
+ let token_url =
56
+ TokenUrl :: new (
57
+ Url :: parse ( "https://github.com/login/oauth/access_token" )
58
+ . expect ( "Invalid token endpoint URL" )
59
+ ) ;
33
60
34
61
// Set up the config for the Github OAuth2 process.
35
62
let client =
36
63
BasicClient :: new ( github_client_id, Some ( github_client_secret) , auth_url, token_url)
37
- . expect ( "failed to create client" )
38
-
39
64
// This example is requesting access to the user's public repos and email.
40
- . add_scope ( "public_repo" )
41
- . add_scope ( "user:email" )
65
+ . add_scope ( Scope :: new ( "public_repo" . to_string ( ) ) )
66
+ . add_scope ( Scope :: new ( "user:email" . to_string ( ) ) )
42
67
43
68
// This example will be running its own server at localhost:8080.
44
69
// See below for the server implementation.
45
- . set_redirect_url ( "http://localhost:8080" ) ;
46
-
47
- let mut rng = thread_rng ( ) ;
48
- // Generate a 128-bit random string for CSRF protection (each time!).
49
- let random_bytes : Vec < u8 > = ( 0 .. 16 ) . map ( |_| rng . gen :: < u8 > ( ) ) . collect ( ) ;
50
- let csrf_state = base64 :: encode ( & random_bytes ) ;
70
+ . set_redirect_url (
71
+ RedirectUrl :: new (
72
+ Url :: parse ( "http://localhost:8080" )
73
+ . expect ( "Invalid redirect URL" )
74
+ )
75
+ ) ;
51
76
52
77
// Generate the authorization URL to which we'll redirect the user.
53
- let authorize_url = client. authorize_url ( csrf_state . clone ( ) ) ;
78
+ let ( authorize_url, csrf_state ) = client. authorize_url ( CsrfToken :: new_random ) ;
54
79
55
80
println ! ( "Open this URL in your browser:\n {}\n " , authorize_url. to_string( ) ) ;
56
81
57
- // These variables will store the code & state retrieved during the authorization process.
58
- let mut code = String :: new ( ) ;
59
- let mut state = String :: new ( ) ;
60
-
61
82
// A very naive implementation of the redirect server.
62
83
let listener = TcpListener :: bind ( "127.0.0.1:8080" ) . unwrap ( ) ;
63
84
for stream in listener. incoming ( ) {
64
- match stream {
65
- Ok ( mut stream) => {
66
- {
67
- let mut reader = BufReader :: new ( & stream) ;
85
+ if let Ok ( mut stream) = stream {
86
+ let code;
87
+ let state;
88
+ {
89
+ let mut reader = BufReader :: new ( & stream) ;
68
90
69
- let mut request_line = String :: new ( ) ;
70
- reader. read_line ( & mut request_line) . unwrap ( ) ;
91
+ let mut request_line = String :: new ( ) ;
92
+ reader. read_line ( & mut request_line) . unwrap ( ) ;
71
93
72
- let redirect_url = request_line. split_whitespace ( ) . nth ( 1 ) . unwrap ( ) ;
73
- let url = Url :: parse ( & ( "http://localhost" . to_string ( ) + redirect_url) ) . unwrap ( ) ;
94
+ let redirect_url = request_line. split_whitespace ( ) . nth ( 1 ) . unwrap ( ) ;
95
+ let url = Url :: parse ( & ( "http://localhost" . to_string ( ) + redirect_url) ) . unwrap ( ) ;
74
96
75
- let code_pair = url. query_pairs ( ) . find ( |pair| {
76
- let & ( ref key, _) = pair;
77
- key == "code"
78
- } ) . unwrap ( ) ;
97
+ let code_pair = url. query_pairs ( ) . find ( |pair| {
98
+ let & ( ref key, _) = pair;
99
+ key == "code"
100
+ } ) . unwrap ( ) ;
79
101
80
- let ( _, value) = code_pair;
81
- code = value. into_owned ( ) ;
102
+ let ( _, value) = code_pair;
103
+ code = AuthorizationCode :: new ( value. into_owned ( ) ) ;
82
104
83
- let state_pair = url. query_pairs ( ) . find ( |pair| {
84
- let & ( ref key, _) = pair;
85
- key == "state"
86
- } ) . unwrap ( ) ;
105
+ let state_pair = url. query_pairs ( ) . find ( |pair| {
106
+ let & ( ref key, _) = pair;
107
+ key == "state"
108
+ } ) . unwrap ( ) ;
87
109
88
- let ( _, value) = state_pair;
89
- state = value. into_owned ( ) ;
90
- }
91
-
92
- let message = "Go back to your terminal :)" ;
93
- let response = format ! ( "HTTP/1.1 200 OK\r \n content-length: {}\r \n \r \n {}" , message. len( ) , message) ;
94
- stream. write_all ( response. as_bytes ( ) ) . unwrap ( ) ;
110
+ let ( _, value) = state_pair;
111
+ state = CsrfToken :: new ( value. into_owned ( ) ) ;
112
+ }
95
113
96
- // The server will terminate itself after collecting the first code.
97
- break ;
114
+ let message = "Go back to your terminal :)" ;
115
+ let response =
116
+ format ! ( "HTTP/1.1 200 OK\r \n content-length: {}\r \n \r \n {}" , message. len( ) , message) ;
117
+ stream. write_all ( response. as_bytes ( ) ) . unwrap ( ) ;
118
+
119
+ println ! ( "Github returned the following code:\n {}\n " , code. secret( ) ) ;
120
+ println ! (
121
+ "Github returned the following state:\n {} (expected `{}`)\n " ,
122
+ state. secret( ) ,
123
+ csrf_state. secret( )
124
+ ) ;
125
+
126
+ // Exchange the code with a token.
127
+ let token_res = client. exchange_code ( code) ;
128
+
129
+ println ! ( "Github returned the following token:\n {:?}\n " , token_res) ;
130
+
131
+ if let Ok ( token) = token_res {
132
+ // NB: Github returns a single comma-separated "scope" parameter instead of multiple
133
+ // space-separated scopes. Github-specific clients can parse this scope into
134
+ // multiple scopes by splitting at the commas. Note that it's not safe for the
135
+ // library to do this by default because RFC 6749 allows scopes to contain commas.
136
+ let scopes =
137
+ if let Some ( scopes_vec) = token. scopes ( ) {
138
+ scopes_vec
139
+ . iter ( )
140
+ . map ( |comma_separated| comma_separated. split ( "," ) )
141
+ . flat_map ( |inner_scopes| inner_scopes)
142
+ . collect :: < Vec < _ > > ( )
143
+ } else {
144
+ Vec :: new ( )
145
+ } ;
146
+ println ! ( "Github returned the following scopes:\n {:?}\n " , scopes) ;
98
147
}
99
- Err ( _) => { } ,
148
+
149
+ // The server will terminate itself after collecting the first code.
150
+ break ;
100
151
}
101
152
} ;
102
-
103
- println ! ( "Github returned the following code:\n {}\n " , code) ;
104
- println ! ( "Github returned the following state:\n {} (expected `{}`)\n " , state, csrf_state) ;
105
-
106
- // Exchange the code with a token.
107
- let token = client. exchange_code ( code) ;
108
-
109
- println ! ( "Github returned the following token:\n {:?}\n " , token) ;
110
153
}
0 commit comments