1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.codelibs.fess.app.web.admin.user;
17
18 import java.util.Base64;
19 import java.util.HashMap;
20 import java.util.Map;
21 import java.util.function.Consumer;
22
23 import org.apache.commons.lang3.ArrayUtils;
24 import org.apache.logging.log4j.LogManager;
25 import org.apache.logging.log4j.Logger;
26 import org.codelibs.core.lang.StringUtil;
27 import org.codelibs.fess.Constants;
28 import org.codelibs.fess.annotation.Secured;
29 import org.codelibs.fess.app.pager.UserPager;
30 import org.codelibs.fess.app.service.GroupService;
31 import org.codelibs.fess.app.service.RoleService;
32 import org.codelibs.fess.app.service.UserService;
33 import org.codelibs.fess.app.web.CrudMode;
34 import org.codelibs.fess.app.web.base.FessAdminAction;
35 import org.codelibs.fess.mylasta.action.FessMessages;
36 import org.codelibs.fess.opensearch.user.exentity.User;
37 import org.codelibs.fess.util.ComponentUtil;
38 import org.codelibs.fess.util.RenderDataUtil;
39 import org.dbflute.optional.OptionalEntity;
40 import org.dbflute.optional.OptionalThing;
41 import org.lastaflute.web.Execute;
42 import org.lastaflute.web.response.HtmlResponse;
43 import org.lastaflute.web.response.render.RenderData;
44 import org.lastaflute.web.ruts.process.ActionRuntime;
45 import org.lastaflute.web.validation.VaErrorHook;
46 import org.lastaflute.web.validation.VaMessenger;
47
48 import jakarta.annotation.Resource;
49
50
51
52
53
54 public class AdminUserAction extends FessAdminAction {
55
56
57
58
59 public AdminUserAction() {
60 super();
61 }
62
63
64 public static final String ROLE = "admin-user";
65
66 private static final Logger logger = LogManager.getLogger(AdminUserAction.class);
67
68
69
70
71 @Resource
72 private UserService userService;
73 @Resource
74 private RoleService roleService;
75 @Resource
76 private GroupService groupService;
77 @Resource
78 private UserPager userPager;
79
80
81
82
83 @Override
84 protected void setupHtmlData(final ActionRuntime runtime) {
85 super.setupHtmlData(runtime);
86 runtime.registerData("helpLink", systemHelper.getHelpLink(fessConfig.getOnlineHelpNameUser()));
87 runtime.registerData("ldapAdminEnabled", fessConfig.isLdapAdminEnabled());
88 }
89
90 @Override
91 protected String getActionRole() {
92 return ROLE;
93 }
94
95
96
97
98
99
100
101
102
103 @Execute
104 @Secured({ ROLE, ROLE + VIEW })
105 public HtmlResponse index() {
106 return asListHtml();
107 }
108
109
110
111
112
113
114
115
116 @Execute
117 @Secured({ ROLE, ROLE + VIEW })
118 public HtmlResponse list(final OptionalThing<Integer> pageNumber, final SearchForm form) {
119 pageNumber.ifPresent(num -> {
120 userPager.setCurrentPageNumber(pageNumber.get());
121 }).orElse(() -> {
122 userPager.setCurrentPageNumber(0);
123 });
124 return asHtml(path_AdminUser_AdminUserJsp).renderWith(data -> {
125 searchPaging(data, form);
126 });
127 }
128
129
130
131
132
133
134
135 @Execute
136 @Secured({ ROLE, ROLE + VIEW })
137 public HtmlResponse search(final SearchForm form) {
138 copyBeanToBean(form, userPager, op -> op.exclude(Constants.PAGER_CONVERSION_RULE));
139 return asHtml(path_AdminUser_AdminUserJsp).renderWith(data -> {
140 searchPaging(data, form);
141 });
142 }
143
144
145
146
147
148
149
150 @Execute
151 @Secured({ ROLE, ROLE + VIEW })
152 public HtmlResponse reset(final SearchForm form) {
153 userPager.clear();
154 return asHtml(path_AdminUser_AdminUserJsp).renderWith(data -> {
155 searchPaging(data, form);
156 });
157 }
158
159
160
161
162
163
164
165 protected void searchPaging(final RenderData data, final SearchForm form) {
166 RenderDataUtil.register(data, "userItems", userService.getUserList(userPager));
167
168 copyBeanToBean(userPager, form, op -> op.include("id"));
169 }
170
171 private void registerForms(final RenderData data) {
172 RenderDataUtil.register(data, "roleItems", roleService.getAvailableRoleList());
173 RenderDataUtil.register(data, "groupItems", groupService.getAvailableGroupList());
174 }
175
176
177
178
179
180
181
182
183
184
185
186
187 @Execute
188 @Secured({ ROLE })
189 public HtmlResponse createnew() {
190 saveToken();
191 return asHtml(path_AdminUser_AdminUserEditJsp).useForm(CreateForm.class, op -> {
192 op.setup(form -> {
193 form.initialize();
194 form.crudMode = CrudMode.CREATE;
195 });
196 }).renderWith(data -> {
197 registerForms(data);
198 });
199 }
200
201
202
203
204
205
206
207 @Execute
208 @Secured({ ROLE })
209 public HtmlResponse edit(final EditForm form) {
210 validate(form, messages -> {}, this::asListHtml);
211 final String id = form.id;
212 userService.getUser(id).ifPresent(entity -> {
213 copyBeanToBean(entity, form, op -> {});
214 }).orElse(() -> {
215 throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, id), this::asListHtml);
216 });
217 resetPassword(form);
218 saveToken();
219 if (form.crudMode.intValue() == CrudMode.EDIT) {
220
221 form.crudMode = CrudMode.DETAILS;
222 return asDetailsHtml();
223 }
224 form.crudMode = CrudMode.EDIT;
225 return asEditHtml();
226 }
227
228
229
230
231
232
233
234
235
236
237
238 @Execute
239 @Secured({ ROLE, ROLE + VIEW })
240 public HtmlResponse details(final int crudMode, final String id) {
241 verifyCrudMode(crudMode, CrudMode.DETAILS, this::asListHtml);
242 saveToken();
243 return asHtml(path_AdminUser_AdminUserDetailsJsp).useForm(EditForm.class, op -> {
244 op.setup(form -> {
245 userService.getUser(id).ifPresent(entity -> {
246 copyBeanToBean(entity, form, copyOp -> {
247 copyOp.excludeNull();
248 });
249 form.crudMode = crudMode;
250 }).orElse(() -> {
251 throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, id), this::asListHtml);
252 });
253 resetPassword(form);
254 });
255 }).renderWith(data -> {
256 registerForms(data);
257 });
258 }
259
260
261
262
263
264
265
266
267
268
269 @Execute
270 @Secured({ ROLE })
271 public HtmlResponse create(final CreateForm form) {
272 verifyCrudMode(form.crudMode, CrudMode.CREATE, this::asListHtml);
273 validate(form, messages -> {}, this::asEditHtml);
274 validateAttributes(form.attributes, v -> throwValidationError(v, this::asEditHtml));
275 verifyPassword(form, this::asEditHtml);
276 verifyToken(this::asEditHtml);
277 getUser(form).ifPresent(entity -> {
278 try {
279 userService.store(entity);
280 logger.info("Created user: {}", entity.getName());
281 saveInfo(messages -> messages.addSuccessCrudCreateCrudTable(GLOBAL));
282 } catch (final Exception e) {
283 logger.warn("Failed to create user: {}", form.name, e);
284 throwValidationError(messages -> messages.addErrorsCrudFailedToCreateCrudTable(GLOBAL, buildThrowableMessage(e)),
285 this::asEditHtml);
286 }
287 }).orElse(() -> {
288 throwValidationError(messages -> messages.addErrorsCrudFailedToCreateInstance(GLOBAL), this::asEditHtml);
289 });
290 return redirect(getClass());
291 }
292
293
294
295
296
297
298
299 @Execute
300 @Secured({ ROLE })
301 public HtmlResponse update(final EditForm form) {
302 verifyCrudMode(form.crudMode, CrudMode.EDIT, this::asListHtml);
303 validate(form, messages -> {}, this::asEditHtml);
304 validateAttributes(form.attributes, v -> throwValidationError(v, this::asEditHtml));
305 verifyPassword(form, this::asEditHtml);
306 verifyToken(this::asEditHtml);
307 getUser(form).ifPresent(entity -> {
308 try {
309 userService.store(entity);
310 logger.info("Updated user: {}", entity.getName());
311 saveInfo(messages -> messages.addSuccessCrudUpdateCrudTable(GLOBAL));
312 } catch (final Exception e) {
313 logger.warn("Failed to update user: {}", form.name, e);
314 throwValidationError(messages -> messages.addErrorsCrudFailedToUpdateCrudTable(GLOBAL, buildThrowableMessage(e)),
315 this::asEditHtml);
316 }
317 }).orElse(() -> {
318 throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, form.id), this::asEditHtml);
319 });
320 return redirect(getClass());
321 }
322
323
324
325
326
327
328
329 @Execute
330 @Secured({ ROLE })
331 public HtmlResponse delete(final EditForm form) {
332 verifyCrudMode(form.crudMode, CrudMode.DETAILS, this::asListHtml);
333 validate(form, messages -> {}, this::asDetailsHtml);
334 getUserBean().ifPresent(u -> {
335 if (u.getFessUser() instanceof User && form.name.equals(u.getUserId())) {
336 throwValidationError(messages -> messages.addErrorsCouldNotDeleteLoggedInUser(GLOBAL), this::asDetailsHtml);
337 }
338 });
339 verifyToken(this::asDetailsHtml);
340 final String id = form.id;
341 userService.getUser(id).ifPresent(entity -> {
342 try {
343 userService.delete(entity);
344 logger.info("Deleted user: {}", entity.getName());
345 saveInfo(messages -> messages.addSuccessCrudDeleteCrudTable(GLOBAL));
346 } catch (final Exception e) {
347 logger.warn("Failed to delete user: {}", form.name, e);
348 throwValidationError(messages -> messages.addErrorsCrudFailedToDeleteCrudTable(GLOBAL, buildThrowableMessage(e)),
349 this::asDetailsHtml);
350 }
351 }).orElse(() -> {
352 throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, id), this::asDetailsHtml);
353 });
354 return redirect(getClass());
355 }
356
357
358
359
360 private static OptionalEntity<User> getEntity(final CreateForm form) {
361 switch (form.crudMode) {
362 case CrudMode.CREATE:
363 return OptionalEntity.of(new User()).map(entity -> {
364 entity.setId(Base64.getUrlEncoder().encodeToString(form.name.getBytes(Constants.CHARSET_UTF_8)));
365 return entity;
366 });
367 case CrudMode.EDIT:
368 if (form instanceof EditForm) {
369 return ComponentUtil.getComponent(UserService.class).getUser(((EditForm) form).id);
370 }
371 break;
372 default:
373 break;
374 }
375 return OptionalEntity.empty();
376 }
377
378
379
380
381
382
383
384 public static OptionalEntity<User> getUser(final CreateForm form) {
385 return getEntity(form).map(entity -> {
386 copyMapToBean(form.attributes, entity, op -> op.exclude(Constants.COMMON_CONVERSION_RULE));
387 copyBeanToBean(form, entity, op -> op.exclude(ArrayUtils.addAll(Constants.COMMON_CONVERSION_RULE, "password")));
388 if (form.crudMode.intValue() == CrudMode.CREATE || StringUtil.isNotBlank(form.password)) {
389 final String encodedPassword = ComponentUtil.getPasswordHashHelper().encode(form.password);
390 entity.setOriginalPassword(form.password);
391 entity.setPassword(encodedPassword);
392 }
393 return entity;
394 });
395 }
396
397
398
399
400
401
402
403
404 protected Map<String, String> createItem(final String label, final String value) {
405 final Map<String, String> map = new HashMap<>(2);
406 map.put(Constants.ITEM_LABEL, label);
407 map.put(Constants.ITEM_VALUE, value);
408 return map;
409 }
410
411
412
413
414
415
416
417
418
419
420
421 protected void verifyPassword(final CreateForm form, final VaErrorHook validationErrorLambda) {
422 if (form.crudMode == CrudMode.CREATE && StringUtil.isBlank(form.password)) {
423 resetPassword(form);
424 throwValidationError(messages -> {
425 messages.addErrorsBlankPassword("password");
426 }, validationErrorLambda);
427 }
428 if (form.password != null && !form.password.equals(form.confirmPassword)) {
429 form.confirmPassword = null;
430 throwValidationError(messages -> {
431 messages.addErrorsInvalidConfirmPassword("confirmPassword");
432 }, validationErrorLambda);
433 }
434 if (StringUtil.isNotBlank(form.password)) {
435 final String validationError = ComponentUtil.getSystemHelper().validatePassword(form.password);
436 if (StringUtil.isNotBlank(validationError)) {
437 resetPassword(form);
438 throwValidationError(messages -> {
439 addPasswordValidationError(messages, validationError);
440 }, validationErrorLambda);
441 }
442 }
443 }
444
445
446
447
448
449
450
451 protected void addPasswordValidationError(final FessMessages messages, final String errorKey) {
452 switch (errorKey) {
453 case "errors.password_length":
454 messages.addErrorsPasswordLength("password", String.valueOf(ComponentUtil.getFessConfig().getPasswordMinLengthAsInteger()));
455 break;
456 case "errors.password_no_uppercase":
457 messages.addErrorsPasswordNoUppercase("password");
458 break;
459 case "errors.password_no_lowercase":
460 messages.addErrorsPasswordNoLowercase("password");
461 break;
462 case "errors.password_no_digit":
463 messages.addErrorsPasswordNoDigit("password");
464 break;
465 case "errors.password_no_special_char":
466 messages.addErrorsPasswordNoSpecialChar("password");
467 break;
468 case "errors.password_is_blacklisted":
469 messages.addErrorsPasswordIsBlacklisted("password");
470 break;
471 default:
472 messages.addErrorsBlankPassword("password");
473 break;
474 }
475 }
476
477
478
479
480
481
482 public static void resetPassword(final CreateForm form) {
483 form.password = null;
484 form.confirmPassword = null;
485 }
486
487
488
489
490
491
492
493 public static void validateAttributes(final Map<String, String> attributes, final Consumer<VaMessenger<FessMessages>> throwError) {
494 ComponentUtil.getLdapManager()
495 .validateUserAttributes(Long.class, attributes,
496 s -> throwError.accept(messages -> messages.addErrorsPropertyTypeLong("attributes." + s, "attributes." + s)));
497 }
498
499
500
501
502
503 private HtmlResponse asListHtml() {
504 return asHtml(path_AdminUser_AdminUserJsp).renderWith(data -> {
505 RenderDataUtil.register(data, "userItems", userService.getUserList(userPager));
506 }).useForm(SearchForm.class, setup -> {
507 setup.setup(form -> {
508 copyBeanToBean(userPager, form, op -> op.include("id"));
509 });
510 });
511 }
512
513 private HtmlResponse asEditHtml() {
514 return asHtml(path_AdminUser_AdminUserEditJsp).renderWith(data -> {
515 registerForms(data);
516 });
517 }
518
519 private HtmlResponse asDetailsHtml() {
520 return asHtml(path_AdminUser_AdminUserDetailsJsp).renderWith(data -> {
521 registerForms(data);
522 });
523 }
524 }