1919import org .elasticsearch .transport .TransportService ;
2020import org .elasticsearch .xpack .core .XPackSettings ;
2121import org .elasticsearch .xpack .core .security .action .user .ChangePasswordRequest ;
22+ import org .elasticsearch .xpack .core .security .authc .Realm ;
23+ import org .elasticsearch .xpack .core .security .authc .file .FileRealmSettings ;
2224import org .elasticsearch .xpack .core .security .authc .support .Hasher ;
2325import org .elasticsearch .xpack .core .security .user .AnonymousUser ;
26+ import org .elasticsearch .xpack .core .security .user .User ;
27+ import org .elasticsearch .xpack .security .authc .Realms ;
2428import org .elasticsearch .xpack .security .authc .esnative .NativeUsersStore ;
25- import org .elasticsearch .xpack .security .authc .file .FileUserPasswdStore ;
29+ import org .elasticsearch .xpack .security .authc .file .FileRealm ;
2630
2731import java .util .Locale ;
32+ import java .util .Optional ;
2833
29- import static org .elasticsearch .core .Strings .format ;
3034import static org .elasticsearch .xpack .core .security .user .UsernamesField .ELASTIC_NAME ;
35+ import static org .elasticsearch .xpack .security .authc .esnative .NativeUsersStore .USER_NOT_FOUND_MESSAGE ;
3136
3237public class TransportChangePasswordAction extends HandledTransportAction <ChangePasswordRequest , ActionResponse .Empty > {
3338
3439 public static final ActionType <ActionResponse .Empty > TYPE = new ActionType <>("cluster:admin/xpack/security/user/change_password" );
3540 private final Settings settings ;
3641 private final NativeUsersStore nativeUsersStore ;
37- private final FileUserPasswdStore fileUserPasswdStore ;
42+ private final Realms realms ;
3843
3944 @ Inject
4045 public TransportChangePasswordAction (
4146 Settings settings ,
4247 TransportService transportService ,
4348 ActionFilters actionFilters ,
4449 NativeUsersStore nativeUsersStore ,
45- FileUserPasswdStore fileUserPasswdStore
50+ Realms realms
4651 ) {
4752 super (TYPE .name (), transportService , actionFilters , ChangePasswordRequest ::new , EsExecutors .DIRECT_EXECUTOR_SERVICE );
4853 this .settings = settings ;
4954 this .nativeUsersStore = nativeUsersStore ;
50- this .fileUserPasswdStore = fileUserPasswdStore ;
55+ this .realms = realms ;
5156 }
5257
5358 @ Override
@@ -71,24 +76,57 @@ protected void doExecute(Task task, ChangePasswordRequest request, ActionListene
7176 return ;
7277 }
7378
74- if (fileUserPasswdStore .userExists (request .username ())) {
75- // File realm users cannot be managed through this API.
76- // unresolved q: is it possible a username is repeated across file and native realms, such that stopping here is incorrect?
77- logger .debug (() -> format ("failed to change password for user [%s]" , request .username ()));
78- ValidationException validationException = new ValidationException ();
79- validationException .addValidationError (
80- "user ["
81- + username
82- + "] is file-based and cannot be managed via this API."
83- + (ELASTIC_NAME .equalsIgnoreCase (username )
84- ? " To update the '" + ELASTIC_NAME + "' user in a cloud deployment, use the console."
85- : "" )
86- );
87- listener .onFailure (validationException );
88- return ;
89- }
79+ // check if user exists in the native realm
80+ nativeUsersStore .getUser (username , new ActionListener <>() {
81+ @ Override
82+ public void onResponse (User user ) {
83+ Optional <Realm > realm = realms .stream ().filter (t -> FileRealmSettings .TYPE .equalsIgnoreCase (t .type ())).findAny ();
84+ if (user == null && realm .isPresent ()) {
85+ // we should check if this request is mistakenly trying to reset a file realm user
86+ FileRealm fileRealm = (FileRealm ) realm .get ();
87+ fileRealm .lookupUser (username , new ActionListener <>() {
88+ @ Override
89+ public void onResponse (User user ) {
90+ if (user != null ) {
91+ ValidationException validationException = new ValidationException ();
92+ validationException .addValidationError (
93+ "user ["
94+ + username
95+ + "] is file-based and cannot be managed via this API."
96+ + (ELASTIC_NAME .equalsIgnoreCase (username )
97+ ? " To update the '" + ELASTIC_NAME + "' user in a cloud deployment, use the console."
98+ : "" )
99+ );
100+ onFailure (validationException );
101+ } else {
102+ onFailure (createUserNotFoundException ());
103+ }
104+ }
90105
91- nativeUsersStore .changePassword (request , listener .safeMap (v -> ActionResponse .Empty .INSTANCE ));
106+ @ Override
107+ public void onFailure (Exception e ) {
108+ listener .onFailure (e );
109+ }
110+ });
111+ } else if (user == null ) {
112+ listener .onFailure (createUserNotFoundException ());
113+ } else {
114+ // safe to proceed
115+ nativeUsersStore .changePassword (request , listener .safeMap (v -> ActionResponse .Empty .INSTANCE ));
116+ }
117+ }
118+
119+ @ Override
120+ public void onFailure (Exception e ) {
121+ listener .onFailure (e );
122+ }
123+ });
124+
125+ }
92126
127+ private static ValidationException createUserNotFoundException () {
128+ ValidationException validationException = new ValidationException ();
129+ validationException .addValidationError (USER_NOT_FOUND_MESSAGE );
130+ return validationException ;
93131 }
94132}
0 commit comments