@@ -25,21 +25,65 @@ static NONCE_COOKIE_NAME: &str = "nonce";
2525use super :: LicenseInfo ;
2626use crate :: {
2727 appstate:: AppState ,
28- db:: { Id , Settings , User } ,
28+ db:: { models :: settings :: OpenidUsernameHandling , Id , Settings , User } ,
2929 enterprise:: {
3030 db:: models:: openid_provider:: OpenIdProvider ,
3131 directory_sync:: sync_user_groups_if_configured, ldap:: utils:: ldap_update_user_state,
3232 limits:: update_counts,
3333 } ,
3434 error:: WebError ,
3535 handlers:: {
36- auth:: create_session,
37- user:: { check_username, prune_username} ,
38- ApiResponse , AuthResponse , SESSION_COOKIE_NAME , SIGN_IN_COOKIE_NAME ,
36+ auth:: create_session, user:: check_username, ApiResponse , AuthResponse , SESSION_COOKIE_NAME ,
37+ SIGN_IN_COOKIE_NAME ,
3938 } ,
4039 server_config,
4140} ;
4241
42+ /// Prune the given username from illegal characters in accordance with the following rules:
43+ ///
44+ /// To enable LDAP sync usernames need to avoid reserved characters.
45+ /// Username requirements:
46+ /// - 64 characters long
47+ /// - only lowercase or uppercase latin alphabet letters (A-Z, a-z) and digits (0-9)
48+ /// - starts with non-special character
49+ /// - only special characters allowed: . - _
50+ /// - no whitespaces
51+ pub fn prune_username ( username : & str , handling : OpenidUsernameHandling ) -> String {
52+ let mut result = username. to_string ( ) ;
53+
54+ // Go through the string and remove any non-alphanumeric characters at the beginning
55+ result = result
56+ . trim_start_matches ( |c : char | !c. is_ascii_alphanumeric ( ) )
57+ . to_string ( ) ;
58+
59+ let is_char_valid = |c : char | c. is_ascii_alphanumeric ( ) || c == '.' || c == '-' || c == '_' ;
60+
61+ match handling {
62+ OpenidUsernameHandling :: RemoveForbidden => {
63+ result. retain ( & is_char_valid) ;
64+ }
65+ OpenidUsernameHandling :: ReplaceForbidden => {
66+ result = result
67+ . chars ( )
68+ . map ( |c| if is_char_valid ( c) { c } else { '_' } )
69+ . collect ( ) ;
70+ }
71+ OpenidUsernameHandling :: PruneEmailDomain => {
72+ if let Some ( at_index) = result. find ( '@' ) {
73+ result. truncate ( at_index) ;
74+ }
75+ result = result
76+ . chars ( )
77+ . map ( |c| if is_char_valid ( c) { c } else { '_' } )
78+ . collect ( ) ;
79+ }
80+ }
81+
82+ result. truncate ( 64 ) ;
83+
84+ result
85+ }
86+
4387/// Create HTTP client and prevent following redirects
4488async fn get_async_http_client ( ) -> Result < reqwest:: Client , WebError > {
4589 reqwest:: Client :: builder ( )
@@ -207,15 +251,16 @@ pub(crate) async fn user_from_claims(
207251 debug ! ( "Username extracted from email ({email:?}): {username})" ) ;
208252 username
209253 } ;
210- let username = prune_username ( username) ;
254+ let settings = Settings :: get_current_settings ( ) ;
255+
256+ let username = prune_username ( username, settings. openid_username_handling ) ;
211257 // Check if the username is valid just in case, not everything can be handled by the pruning.
212258 check_username ( & username) ?;
213259
214260 // Get the *sub* claim from the token.
215261 let sub = token_claims. subject ( ) . to_string ( ) ;
216262
217263 // Handle logging in or creating user.
218- let settings = Settings :: get_current_settings ( ) ;
219264 let user = match User :: find_by_sub ( pool, & sub)
220265 . await
221266 . map_err ( |err| WebError :: Authorization ( err. to_string ( ) ) ) ?
@@ -557,3 +602,75 @@ pub(crate) async fn auth_callback(
557602 unimplemented ! ( "Impossible to get here" ) ;
558603 }
559604}
605+
606+ #[ cfg( test) ]
607+ mod test {
608+ use super :: * ;
609+
610+ #[ test]
611+ fn test_prune_username ( ) {
612+ // Test RemoveForbidden handling
613+ let handling_remove = OpenidUsernameHandling :: RemoveForbidden ;
614+ assert_eq ! ( prune_username( "zenek" , handling_remove) , "zenek" ) ;
615+ assert_eq ! ( prune_username( "zenek34" , handling_remove) , "zenek34" ) ;
616+ assert_eq ! ( prune_username( "zenek@34" , handling_remove) , "zenek34" ) ;
617+ assert_eq ! ( prune_username( "first.last" , handling_remove) , "first.last" ) ;
618+ assert_eq ! ( prune_username( "__zenek__" , handling_remove) , "zenek__" ) ;
619+ assert_eq ! ( prune_username( "zenek?" , handling_remove) , "zenek" ) ;
620+ assert_eq ! ( prune_username( "zenek!" , handling_remove) , "zenek" ) ;
621+ assert_eq ! (
622+ prune_username(
623+ "averylongnameeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" ,
624+ handling_remove
625+ ) ,
626+ "averylongnameeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
627+ ) ;
628+ assert_eq ! ( prune_username( "" , handling_remove) , "" ) ;
629+ assert_eq ! ( prune_username( "!@#$%^&*()" , handling_remove) , "" ) ;
630+ assert_eq ! ( prune_username( "!zenek" , handling_remove) , "zenek" ) ;
631+ assert_eq ! ( prune_username( "...zenek" , handling_remove) , "zenek" ) ;
632+
633+ // Test ReplaceForbidden handling
634+ let handling_replace = OpenidUsernameHandling :: ReplaceForbidden ;
635+ assert_eq ! ( prune_username( "zenek" , handling_replace) , "zenek" ) ;
636+ assert_eq ! ( prune_username( "zenek34" , handling_replace) , "zenek34" ) ;
637+ assert_eq ! ( prune_username( "zenek@34" , handling_replace) , "zenek_34" ) ;
638+ assert_eq ! ( prune_username( "first.last" , handling_replace) , "first.last" ) ;
639+ assert_eq ! ( prune_username( "__zenek__" , handling_replace) , "zenek__" ) ;
640+ assert_eq ! ( prune_username( "zenek?" , handling_replace) , "zenek_" ) ;
641+ assert_eq ! ( prune_username( "zenek!" , handling_replace) , "zenek_" ) ;
642+ assert_eq ! (
643+ prune_username(
644+ "averylongnameeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" ,
645+ handling_replace
646+ ) ,
647+ "averylongnameeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
648+ ) ;
649+
650+ // Test PruneEmailDomain handling
651+ let handling_prune_email = OpenidUsernameHandling :: PruneEmailDomain ;
652+ assert_eq ! (
653+ prune_username
( "[email protected] " , handling_prune_email
) , 654+ "zenek"
655+ ) ;
656+ assert_eq ! (
657+ prune_username
( "[email protected] " , handling_prune_email
) , 658+ "user.name"
659+ ) ;
660+ assert_eq ! (
661+ prune_username
( "[email protected] " , handling_prune_email
) , 662+ "invalid_chars_"
663+ ) ;
664+ assert_eq ! (
665+ prune_username
( "multiple@[email protected] " , handling_prune_email
) , 666+ "multiple"
667+ ) ;
668+ assert_eq ! (
669+ prune_username(
670+ "averylongnameeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee@domain.com" ,
671+ handling_prune_email
672+ ) ,
673+ "averylongnameeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
674+ ) ;
675+ }
676+ }
0 commit comments