View Javadoc
1   /*
2    * Copyright 2012-2025 CodeLibs Project and the Others.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13   * either express or implied. See the License for the specific language
14   * governing permissions and limitations under the License.
15   */
16  /**
17   */
18  package org.codelibs.fess.app.web.profile;
19  
20  import org.apache.logging.log4j.LogManager;
21  import org.apache.logging.log4j.Logger;
22  import org.codelibs.core.lang.StringUtil;
23  import org.codelibs.fess.app.service.UserService;
24  import org.codelibs.fess.app.web.base.FessSearchAction;
25  import org.codelibs.fess.app.web.base.login.LocalUserCredential;
26  import org.codelibs.fess.app.web.login.LoginAction;
27  import org.codelibs.fess.mylasta.action.FessMessages;
28  import org.codelibs.fess.util.ComponentUtil;
29  import org.lastaflute.web.Execute;
30  import org.lastaflute.web.response.HtmlResponse;
31  import org.lastaflute.web.validation.VaErrorHook;
32  
33  import jakarta.annotation.Resource;
34  
35  /**
36   * Action for user profile operations.
37   */
38  public class ProfileAction extends FessSearchAction {
39  
40      /**
41       * Default constructor.
42       */
43      public ProfileAction() {
44          super();
45      }
46  
47      private static final Logger logger = LogManager.getLogger(ProfileAction.class);
48  
49      // ===================================================================================
50      // Constant
51      //
52  
53      // ===================================================================================
54      // Attribute
55      //
56      @Resource
57      private UserService userService;
58  
59      // ===================================================================================
60      // Hook
61      // ======
62  
63      // ===================================================================================
64      // Search Execute
65      // ==============
66  
67      /**
68       * Displays the profile index page.
69       *
70       * @return the HTML response
71       */
72      @Execute
73      public HtmlResponse index() {
74          return asIndexHtml();
75      }
76  
77      /**
78       * Changes the user password.
79       *
80       * @param form the profile form
81       * @return the HTML response
82       */
83      @Execute
84      public HtmlResponse changePassword(final ProfileForm form) {
85          final VaErrorHook toIndexPage = () -> {
86              form.clearSecurityInfo();
87              return asIndexHtml();
88          };
89          validatePasswordForm(form, toIndexPage);
90          if (!getUserBean().isPresent()) {
91              logger.warn("User session not found during password change");
92              return redirect(LoginAction.class);
93          }
94          final String username = getUserBean().get().getUserId();
95          try {
96              userService.changePassword(username, form.newPassword);
97              saveInfo(messages -> messages.addSuccessChangedPassword(GLOBAL));
98          } catch (final Exception e) {
99              logger.warn("Failed to change password for {}", username, e);
100             throwValidationError(messages -> messages.addErrorsFailedToChangePassword(GLOBAL), toIndexPage);
101         }
102         return redirect(getClass());
103     }
104 
105     private void validatePasswordForm(final ProfileForm form, final VaErrorHook validationErrorLambda) {
106         validate(form, messages -> {}, validationErrorLambda);
107 
108         if (!form.newPassword.equals(form.confirmNewPassword)) {
109             form.newPassword = null;
110             form.confirmNewPassword = null;
111             throwValidationError(messages -> {
112                 messages.addErrorsInvalidConfirmPassword(GLOBAL);
113             }, validationErrorLambda);
114         }
115 
116         final String validationError = ComponentUtil.getSystemHelper().validatePassword(form.newPassword);
117         if (StringUtil.isNotBlank(validationError)) {
118             form.newPassword = null;
119             form.confirmNewPassword = null;
120             throwValidationError(messages -> {
121                 addPasswordValidationError(messages, validationError);
122             }, validationErrorLambda);
123         }
124 
125         getUserBean().ifPresent(user -> {
126             final String userId = user.getUserId();
127             fessLoginAssist.findLoginUser(new LocalUserCredential(userId, form.oldPassword)).orElseGet(() -> {
128                 throwValidationError(messages -> {
129                     messages.addErrorsNoUserForChangingPassword(GLOBAL);
130                 }, validationErrorLambda);
131                 return null;
132             });
133         }).orElse(() -> {
134             throwValidationError(messages -> {
135                 messages.addErrorsLoginError(GLOBAL);
136             }, validationErrorLambda);
137         });
138     }
139 
140     /**
141      * Adds a password validation error message to the messages object based on the error key.
142      *
143      * @param messages the FessMessages object to add the error to
144      * @param errorKey the error key identifying the type of password validation error
145      */
146     protected void addPasswordValidationError(final FessMessages messages, final String errorKey) {
147         switch (errorKey) {
148         case "errors.password_length":
149             messages.addErrorsPasswordLength(GLOBAL, String.valueOf(ComponentUtil.getFessConfig().getPasswordMinLengthAsInteger()));
150             break;
151         case "errors.password_no_uppercase":
152             messages.addErrorsPasswordNoUppercase(GLOBAL);
153             break;
154         case "errors.password_no_lowercase":
155             messages.addErrorsPasswordNoLowercase(GLOBAL);
156             break;
157         case "errors.password_no_digit":
158             messages.addErrorsPasswordNoDigit(GLOBAL);
159             break;
160         case "errors.password_no_special_char":
161             messages.addErrorsPasswordNoSpecialChar(GLOBAL);
162             break;
163         case "errors.password_is_blacklisted":
164             messages.addErrorsPasswordIsBlacklisted(GLOBAL);
165             break;
166         default:
167             messages.addErrorsBlankPassword(GLOBAL);
168             break;
169         }
170     }
171 
172     /**
173      * Returns the index HTML response.
174      *
175      * @return the HTML response
176      */
177     protected HtmlResponse asIndexHtml() {
178         return getUserBean().map(u -> asHtml(virtualHost(path_Profile_IndexJsp)).useForm(ProfileForm.class))
179                 .orElseGet(() -> redirect(LoginAction.class));
180     }
181 }