diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7115b041..826a66c0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,6 +25,7 @@ jobs: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: rust: - 1.45.0 diff --git a/Cargo.toml b/Cargo.toml index 01bcf737..abf54390 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,10 @@ nightly = [] [dependencies] base64 = "0.13" # Disable 'time' dependency since it triggers RUSTSEC-2020-0071 and we don't need it. -chrono = { version = "0.4", default-features = false, features = ["clock", "std"] } +chrono = { version = "0.4", default-features = false, features = [ + "clock", + "std", +] } thiserror = "1.0" http = "0.2" itertools = "0.9" @@ -48,6 +51,9 @@ num-bigint = "0.4.3" color-backtrace = { version = "0.4" } env_logger = "0.7" pretty_assertions = "0.6" -reqwest_ = { package = "reqwest", features = ["blocking", "rustls-tls"], version = "0.11", default-features = false } +reqwest_ = { package = "reqwest", features = [ + "blocking", + "rustls-tls", +], version = "0.11", default-features = false } retry = "1.0" anyhow = "1.0" diff --git a/examples/gitlab.rs b/examples/gitlab.rs index b73e2bec..a0a79457 100644 --- a/examples/gitlab.rs +++ b/examples/gitlab.rs @@ -100,106 +100,99 @@ fn main() { .add_scope(Scope::new("profile".to_string())) .url(); - println!( - "Open this URL in your browser:\n{}\n", - authorize_url.to_string() - ); + println!("Open this URL in your browser:\n{}\n", authorize_url); // A very naive implementation of the redirect server. let listener = TcpListener::bind("127.0.0.1:8080").unwrap(); - for stream in listener.incoming() { - if let Ok(mut stream) = stream { - let code; - let state; - { - 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_pair = url - .query_pairs() - .find(|pair| { - let &(ref key, _) = pair; - key == "code" - }) - .unwrap(); - - let (_, value) = code_pair; - code = AuthorizationCode::new(value.into_owned()); - - let state_pair = url - .query_pairs() - .find(|pair| { - let &(ref key, _) = pair; - key == "state" - }) - .unwrap(); - - let (_, value) = state_pair; - state = CsrfToken::new(value.into_owned()); - } - - 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(); - - println!("GitLab returned the following code:\n{}\n", code.secret()); - println!( - "GitLab 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) - .request(http_client) - .unwrap_or_else(|err| { - handle_error(&err, "Failed to contact token endpoint"); - unreachable!(); - }); - - println!( - "GitLab returned access token:\n{}\n", - token_response.access_token().secret() - ); - println!("GitLab 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!("GitLab returned ID token: {:?}\n", id_token_claims); - - let userinfo_claims: UserInfoClaims = client - .user_info(token_response.access_token().to_owned(), None) - .unwrap_or_else(|err| { - handle_error(&err, "No user info endpoint"); - unreachable!(); - }) - .request(http_client) - .unwrap_or_else(|err| { - handle_error(&err, "Failed requesting user info"); - unreachable!(); - }); - println!("GitLab returned UserInfo: {:?}", userinfo_claims); - - // The server will terminate itself - break; - } + + // Accept one connection + let (mut stream, _) = listener.accept().unwrap(); + let code; + let state; + { + 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_pair = url + .query_pairs() + .find(|pair| { + let &(ref key, _) = pair; + key == "code" + }) + .unwrap(); + + let (_, value) = code_pair; + code = AuthorizationCode::new(value.into_owned()); + + let state_pair = url + .query_pairs() + .find(|pair| { + let &(ref key, _) = pair; + key == "state" + }) + .unwrap(); + + let (_, value) = state_pair; + state = CsrfToken::new(value.into_owned()); } + + 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(); + + println!("GitLab returned the following code:\n{}\n", code.secret()); + println!( + "GitLab 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) + .request(http_client) + .unwrap_or_else(|err| { + handle_error(&err, "Failed to contact token endpoint"); + unreachable!(); + }); + + println!( + "GitLab returned access token:\n{}\n", + token_response.access_token().secret() + ); + println!("GitLab 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!("GitLab returned ID token: {:?}\n", id_token_claims); + + let userinfo_claims: UserInfoClaims = client + .user_info(token_response.access_token().to_owned(), None) + .unwrap_or_else(|err| { + handle_error(&err, "No user info endpoint"); + unreachable!(); + }) + .request(http_client) + .unwrap_or_else(|err| { + handle_error(&err, "Failed requesting user info"); + unreachable!(); + }); + println!("GitLab returned UserInfo: {:?}", userinfo_claims); } diff --git a/examples/google.rs b/examples/google.rs index 91f67917..87000896 100644 --- a/examples/google.rs +++ b/examples/google.rs @@ -139,108 +139,102 @@ fn main() { .add_scope(Scope::new("profile".to_string())) .url(); - println!( - "Open this URL in your browser:\n{}\n", - authorize_url.to_string() - ); + println!("Open this URL in your browser:\n{}\n", authorize_url); // A very naive implementation of the redirect server. let listener = TcpListener::bind("127.0.0.1:8080").unwrap(); - for stream in listener.incoming() { - if let Ok(mut stream) = stream { - let code; - let state; - { - 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_pair = url - .query_pairs() - .find(|pair| { - let &(ref key, _) = pair; - key == "code" - }) - .unwrap(); - - let (_, value) = code_pair; - code = AuthorizationCode::new(value.into_owned()); - - let state_pair = url - .query_pairs() - .find(|pair| { - let &(ref key, _) = pair; - key == "state" - }) - .unwrap(); - - let (_, value) = state_pair; - state = CsrfToken::new(value.into_owned()); - } - - 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(); - - 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) - .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) - .expect("no revocation_uri configured") - .request(http_client) - .unwrap_or_else(|err| { - handle_error(&err, "Failed to contact token revocation endpoint"); - unreachable!(); - }); - - // The server will terminate itself after revoking the token. - break; - } + + // Accept one connection + let (mut stream, _) = listener.accept().unwrap(); + + let code; + let state; + { + 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_pair = url + .query_pairs() + .find(|pair| { + let &(ref key, _) = pair; + key == "code" + }) + .unwrap(); + + let (_, value) = code_pair; + code = AuthorizationCode::new(value.into_owned()); + + let state_pair = url + .query_pairs() + .find(|pair| { + let &(ref key, _) = pair; + key == "state" + }) + .unwrap(); + + let (_, value) = state_pair; + state = CsrfToken::new(value.into_owned()); } + + 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(); + + 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) + .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) + .expect("no revocation_uri configured") + .request(http_client) + .unwrap_or_else(|err| { + handle_error(&err, "Failed to contact token revocation endpoint"); + unreachable!(); + }); } diff --git a/src/claims.rs b/src/claims.rs index dbed9c5b..c45ae3ef 100644 --- a/src/claims.rs +++ b/src/claims.rs @@ -194,29 +194,29 @@ where // When another struct (i.e., additional claims) is co-flattened with this one, only include // fields in that other struct which are not part of this struct. fn should_include(field_name: &str) -> bool { - match split_language_tag_key(field_name) { + !matches!( + split_language_tag_key(field_name), ("sub", None) - | ("name", _) - | ("given_name", _) - | ("family_name", _) - | ("middle_name", _) - | ("nickname", _) - | ("preferred_username", None) - | ("profile", _) - | ("picture", _) - | ("website", _) - | ("email", None) - | ("email_verified", None) - | ("gender", None) - | ("birthday", None) - | ("zoneinfo", None) - | ("locale", None) - | ("phone_number", None) - | ("phone_number_verified", None) - | ("address", None) - | ("updated_at", None) => false, - _ => true, - } + | ("name", _) + | ("given_name", _) + | ("family_name", _) + | ("middle_name", _) + | ("nickname", _) + | ("preferred_username", None) + | ("profile", _) + | ("picture", _) + | ("website", _) + | ("email", None) + | ("email_verified", None) + | ("gender", None) + | ("birthday", None) + | ("zoneinfo", None) + | ("locale", None) + | ("phone_number", None) + | ("phone_number_verified", None) + | ("address", None) + | ("updated_at", None) + ) } } impl<'de, GC> Deserialize<'de> for StandardClaims diff --git a/src/core/crypto.rs b/src/core/crypto.rs index bfc0e9ed..03d9cf9e 100644 --- a/src/core/crypto.rs +++ b/src/core/crypto.rs @@ -124,7 +124,7 @@ pub fn verify_ec_signature( msg: &[u8], signature: &[u8], ) -> Result<(), SignatureVerificationError> { - let (x, y, crv) = ec_public_key(&key).map_err(SignatureVerificationError::InvalidKey)?; + let (x, y, crv) = ec_public_key(key).map_err(SignatureVerificationError::InvalidKey)?; if *crv == CoreJsonCurveType::P521 { return Err(SignatureVerificationError::UnsupportedAlg( "P521".to_string(), diff --git a/src/core/jwk.rs b/src/core/jwk.rs index f9734ef3..67e322e4 100644 --- a/src/core/jwk.rs +++ b/src/core/jwk.rs @@ -371,10 +371,7 @@ impl CoreRsaPrivateSigningKey { /// /// RFC 7468 Lax Parsing fn keep_char_in_lax_base64_parsing(input: char) -> bool { - match input { - ' ' | '\n' | '\t' | '\r' | '\x0b' | '\x0c' => false, - _ => true, - } + !matches!(input, ' ' | '\n' | '\t' | '\r' | '\x0b' | '\x0c') } } impl @@ -503,18 +500,10 @@ pub enum CoreJsonWebKeyUse { } impl JsonWebKeyUse for CoreJsonWebKeyUse { fn allows_signature(&self) -> bool { - if let CoreJsonWebKeyUse::Signature = *self { - true - } else { - false - } + matches!(*self, CoreJsonWebKeyUse::Signature) } fn allows_encryption(&self) -> bool { - if let CoreJsonWebKeyUse::Encryption = *self { - true - } else { - false - } + matches!(*self, CoreJsonWebKeyUse::Encryption) } } diff --git a/src/http_utils.rs b/src/http_utils.rs index c6be2478..18f461c9 100644 --- a/src/http_utils.rs +++ b/src/http_utils.rs @@ -10,12 +10,12 @@ pub const BEARER: &str = "Bearer"; // The [essence](https://mimesniff.spec.whatwg.org/#mime-type-essence) is the / // representation. pub fn content_type_has_essence(content_type: &HeaderValue, expected_essence: &str) -> bool { + #[allow(clippy::or_fun_call)] content_type .to_str() .ok() .filter(|ct| { - ct[..ct.find(';').unwrap_or_else(|| ct.len())].to_lowercase() - == expected_essence.to_lowercase() + ct[..ct.find(';').unwrap_or(ct.len())].to_lowercase() == expected_essence.to_lowercase() }) .is_some() } @@ -27,7 +27,7 @@ pub fn check_content_type(headers: &HeaderMap, expected_content_type: &str) -> R // Section 3.1.1.1 of RFC 7231 indicates that media types are case insensitive and // may be followed by optional whitespace and/or a parameter (e.g., charset). // See https://tools.ietf.org/html/rfc7231#section-3.1.1.1. - if !content_type_has_essence(&content_type, expected_content_type) { + if !content_type_has_essence(content_type, expected_content_type) { Err( format!( "Unexpected response Content-Type: {:?}, should be `{}`", diff --git a/src/registration.rs b/src/registration.rs index c0cf7c92..9989d037 100644 --- a/src/registration.rs +++ b/src/registration.rs @@ -511,11 +511,7 @@ where .map_err(ClientRegistrationError::Serialize)? .into_bytes(); - let auth_header_opt = if let Some(initial_access_token) = self.initial_access_token() { - Some(auth_bearer(initial_access_token)) - } else { - None - }; + let auth_header_opt = self.initial_access_token().map(auth_bearer); let mut headers = HeaderMap::new(); headers.append(ACCEPT, HeaderValue::from_static(MIME_TYPE_JSON)); @@ -1076,7 +1072,7 @@ mod tests { assert_eq!( client_metadata.jwks(), Some(&JsonWebKeySet::new(vec![serde_json::from_str( - &TEST_RSA_PUB_KEY + TEST_RSA_PUB_KEY ) .unwrap()],)) ); @@ -1136,7 +1132,7 @@ mod tests { *client_metadata.default_max_age().unwrap(), Duration::from_secs(3600) ); - assert_eq!(client_metadata.require_auth_time().unwrap(), true); + assert!(client_metadata.require_auth_time().unwrap()); assert_eq!( *client_metadata.default_acr_values().unwrap(), vec![ @@ -1403,7 +1399,7 @@ mod tests { assert_eq!( registration_response.jwks(), Some(&JsonWebKeySet::new(vec![serde_json::from_str( - &TEST_RSA_PUB_KEY + TEST_RSA_PUB_KEY ) .unwrap()],)), ); @@ -1481,7 +1477,7 @@ mod tests { *registration_response.default_max_age().unwrap(), Duration::from_secs(3600) ); - assert_eq!(registration_response.require_auth_time().unwrap(), true); + assert!(registration_response.require_auth_time().unwrap()); assert_eq!( *registration_response.default_acr_values().unwrap(), vec![ diff --git a/src/user_info.rs b/src/user_info.rs index d3799d31..5066c859 100644 --- a/src/user_info.rs +++ b/src/user_info.rs @@ -130,7 +130,7 @@ where .map(ToOwned::to_owned) .unwrap_or_else(|| HeaderValue::from_static(MIME_TYPE_JSON)) { - ref content_type if content_type_has_essence(&content_type, MIME_TYPE_JSON) => { + ref content_type if content_type_has_essence(content_type, MIME_TYPE_JSON) => { if self.require_signed_response { return Err(UserInfoError::ClaimsVerification( ClaimsVerificationError::NoSignature, @@ -141,7 +141,7 @@ where self.signed_response_verifier.expected_subject(), ) } - ref content_type if content_type_has_essence(&content_type, MIME_TYPE_JWT) => { + ref content_type if content_type_has_essence(content_type, MIME_TYPE_JWT) => { let jwt_str = String::from_utf8(http_response.body).map_err(|_| { UserInfoError::Other("response body has invalid UTF-8 encoding".to_string()) })?; @@ -231,7 +231,7 @@ where RE: std::error::Error + 'static, { let user_info = serde_path_to_error::deserialize::<_, UserInfoClaimsImpl>( - &mut serde_json::Deserializer::from_slice(&user_info_json), + &mut serde_json::Deserializer::from_slice(user_info_json), ) .map_err(UserInfoError::Parse)?; diff --git a/src/verification.rs b/src/verification.rs index c7fd4d78..3d1f9618 100644 --- a/src/verification.rs +++ b/src/verification.rs @@ -765,9 +765,9 @@ where Ok(partially_verified_claims) } - fn verify_claims<'b, AC, GC, N>( + fn verify_claims( &self, - partially_verified_claims: &'b IdTokenClaims, + partially_verified_claims: &'_ IdTokenClaims, nonce_verifier: N, ) -> Result<(), ClaimsVerificationError> where diff --git a/tests/rp_certification_dynamic.rs b/tests/rp_certification_dynamic.rs index 566b1f9b..1c95fc15 100644 --- a/tests/rp_certification_dynamic.rs +++ b/tests/rp_certification_dynamic.rs @@ -134,7 +134,7 @@ fn rp_registration_dynamic() { "{}/{}/registration?client_id={}", CERTIFICATION_BASE_URL, RP_NAME, - registration_response.client_id().to_string() + **registration_response.client_id() ), registration_response .registration_client_uri() diff --git a/tests/rp_common/mod.rs b/tests/rp_common/mod.rs index f5d75b0c..0d3ed275 100644 --- a/tests/rp_common/mod.rs +++ b/tests/rp_common/mod.rs @@ -36,7 +36,7 @@ pub fn set_test_id(test_id: &'static str) { #[macro_export] macro_rules! log_error { ($($args:tt)+) => { - error!("[{}] {}", rp_common::get_test_id(), format!($($args)+)); + error!("[{}] {}", rp_common::get_test_id(), format!($($args)+)) } } #[macro_export] @@ -112,7 +112,7 @@ where cur_fail = cause.source(); } error!("[{}] {}", get_test_id(), err_msg); - panic!(msg); + panic!("{}", msg); } } } @@ -161,7 +161,7 @@ where .registration_endpoint() .expect("provider does not support dynamic registration"); registration_request_post - .register(®istration_endpoint, http_client) + .register(registration_endpoint, http_client) .expect(&format!( "Failed to register client at {:?}", registration_endpoint