@@ -1192,7 +1192,8 @@ impl AuthorizationManager {
11921192 drop ( attempts) ;
11931193
11941194 let current_scopes = self . current_scopes . read ( ) . await . clone ( ) ;
1195- let upgraded_scopes = Self :: compute_scope_union ( & current_scopes, required_scope) ;
1195+ let mut upgraded_scopes = Self :: compute_scope_union ( & current_scopes, required_scope) ;
1196+ self . add_offline_access_if_supported ( & mut upgraded_scopes) ;
11961197
11971198 debug ! (
11981199 "Requesting scope upgrade: current={:?}, required={}, union={:?}" ,
@@ -1425,8 +1426,10 @@ impl AuthorizationManager {
14251426
14261427 let refresh_token_value = RefreshToken :: new ( refresh_token. secret ( ) . to_string ( ) ) ;
14271428 let mut refresh_request = oauth_client. exchange_refresh_token ( & refresh_token_value) ;
1428- for scope in & stored_credentials. granted_scopes {
1429- refresh_request = refresh_request. add_scope ( Scope :: new ( scope. clone ( ) ) ) ;
1429+ let mut refresh_scopes = stored_credentials. granted_scopes ;
1430+ self . add_offline_access_if_supported ( & mut refresh_scopes) ;
1431+ for scope in refresh_scopes {
1432+ refresh_request = refresh_request. add_scope ( Scope :: new ( scope) ) ;
14301433 }
14311434 let token_result = refresh_request
14321435 . request_async ( & OAuthReqwestClient ( self . http_client . clone ( ) ) )
@@ -3912,6 +3915,29 @@ mod tests {
39123915 ) ;
39133916 }
39143917
3918+ #[ tokio:: test]
3919+ async fn scope_upgrade_adds_offline_access_when_as_supports_it ( ) {
3920+ let mut mgr = manager_with_metadata ( Some ( AuthorizationMetadata {
3921+ authorization_endpoint : "http://localhost/authorize" . to_string ( ) ,
3922+ token_endpoint : "http://localhost/token" . to_string ( ) ,
3923+ scopes_supported : Some ( vec ! [ "profile" . to_string( ) , "offline_access" . to_string( ) ] ) ,
3924+ ..Default :: default ( )
3925+ } ) )
3926+ . await ;
3927+ mgr. configure_client_id ( "my-client" ) . unwrap ( ) ;
3928+ * mgr. current_scopes . write ( ) . await = vec ! [ "profile" . to_string( ) ] ;
3929+
3930+ let auth_url = mgr. request_scope_upgrade ( "email" ) . await . unwrap ( ) ;
3931+ let parsed = Url :: parse ( & auth_url) . unwrap ( ) ;
3932+ let scope = parsed
3933+ . query_pairs ( )
3934+ . find_map ( |( key, value) | ( key == "scope" ) . then ( || value. into_owned ( ) ) )
3935+ . expect ( "scope should be present" ) ;
3936+ let mut scope_parts: Vec < & str > = scope. split_whitespace ( ) . collect ( ) ;
3937+ scope_parts. sort_unstable ( ) ;
3938+ assert_eq ! ( scope_parts, vec![ "email" , "offline_access" , "profile" ] ) ;
3939+ }
3940+
39153941 #[ test]
39163942 fn scope_upgrade_config_default_values ( ) {
39173943 let config = ScopeUpgradeConfig :: default ( ) ;
@@ -4461,6 +4487,44 @@ mod tests {
44614487 assert_eq ! ( scope_parts, vec![ "read" , "write" ] ) ;
44624488 }
44634489
4490+ #[ tokio:: test]
4491+ async fn refresh_token_adds_offline_access_when_as_supports_it ( ) {
4492+ let ( base_url, captured) = start_token_server ( ) . await ;
4493+
4494+ let mut manager = manager_with_metadata ( Some ( AuthorizationMetadata {
4495+ authorization_endpoint : format ! ( "{}/authorize" , base_url) ,
4496+ token_endpoint : format ! ( "{}/token" , base_url) ,
4497+ scopes_supported : Some ( vec ! [ "read" . to_string( ) , "offline_access" . to_string( ) ] ) ,
4498+ ..Default :: default ( )
4499+ } ) )
4500+ . await ;
4501+ manager. configure_client ( test_client_config ( ) ) . unwrap ( ) ;
4502+
4503+ let stored = StoredCredentials {
4504+ client_id : "my-client" . to_string ( ) ,
4505+ token_response : Some ( make_token_response_with_refresh (
4506+ "old-token" ,
4507+ "my-refresh-token" ,
4508+ ) ) ,
4509+ granted_scopes : vec ! [ "read" . to_string( ) ] ,
4510+ token_received_at : Some ( AuthorizationManager :: now_epoch_secs ( ) ) ,
4511+ } ;
4512+ manager. credential_store . save ( stored) . await . unwrap ( ) ;
4513+
4514+ manager. refresh_token ( ) . await . unwrap ( ) ;
4515+
4516+ let body = captured. lock ( ) . unwrap ( ) . take ( ) . unwrap ( ) ;
4517+ let params: std:: collections:: HashMap < _ , _ > = url:: form_urlencoded:: parse ( body. as_bytes ( ) )
4518+ . into_owned ( )
4519+ . collect ( ) ;
4520+ let scope = params
4521+ . get ( "scope" )
4522+ . expect ( "scope should be present in refresh request" ) ;
4523+ let mut scope_parts: Vec < & str > = scope. split_whitespace ( ) . collect ( ) ;
4524+ scope_parts. sort_unstable ( ) ;
4525+ assert_eq ! ( scope_parts, vec![ "offline_access" , "read" ] ) ;
4526+ }
4527+
44644528 #[ tokio:: test]
44654529 async fn refresh_token_omits_scope_when_granted_scopes_is_empty ( ) {
44664530 let ( base_url, captured) = start_token_server ( ) . await ;
0 commit comments