1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.codelibs.fess.app.service;
17
18 import java.util.List;
19
20 import org.apache.logging.log4j.LogManager;
21 import org.apache.logging.log4j.Logger;
22 import org.codelibs.core.beans.util.BeanUtil;
23 import org.codelibs.core.lang.StringUtil;
24 import org.codelibs.fess.Constants;
25 import org.codelibs.fess.app.pager.UserPager;
26 import org.codelibs.fess.exception.FessUserNotFoundException;
27 import org.codelibs.fess.mylasta.direction.FessConfig;
28 import org.codelibs.fess.opensearch.user.cbean.UserCB;
29 import org.codelibs.fess.opensearch.user.exbhv.UserBhv;
30 import org.codelibs.fess.opensearch.user.exentity.User;
31 import org.codelibs.fess.util.ComponentUtil;
32 import org.dbflute.cbean.result.PagingResultBean;
33 import org.dbflute.optional.OptionalEntity;
34
35 import jakarta.annotation.Resource;
36
37
38
39
40
41
42
43 public class UserService {
44
45 private static final Logger logger = LogManager.getLogger(UserService.class);
46
47
48
49
50 public UserService() {
51
52 }
53
54
55 @Resource
56 protected UserBhv userBhv;
57
58
59 @Resource
60 protected FessConfig fessConfig;
61
62
63
64
65
66
67
68
69 public List<User> getUserList(final UserPager userPager) {
70
71 final PagingResultBean<User> userList = userBhv.selectPage(cb -> {
72 cb.paging(userPager.getPageSize(), userPager.getCurrentPageNumber());
73 setupListCondition(cb, userPager);
74 });
75
76
77 BeanUtil.copyBeanToBean(userList, userPager, option -> option.include(Constants.PAGER_CONVERSION_RULE));
78 userPager.setPageNumberList(userList.pageRange(op -> {
79 op.rangeSize(fessConfig.getPagingPageRangeSizeAsInteger());
80 }).createPageNumberList());
81
82 return userList;
83 }
84
85
86
87
88
89
90
91
92 public OptionalEntity<User> getUser(final String id) {
93 return userBhv.selectByPK(id).map(u -> ComponentUtil.getAuthenticationManager().load(u));
94 }
95
96
97
98
99
100
101
102 public OptionalEntity<User> getUserByName(final String username) {
103 return userBhv.selectEntity(cb -> {
104 cb.query().setName_Equal(username);
105 });
106 }
107
108
109
110
111
112
113
114
115 public void store(final User user) {
116 final String username = user.getName();
117 final boolean isUpdate = StringUtil.isNotBlank(user.getId());
118
119 if (logger.isDebugEnabled()) {
120 logger.debug("User {} operation initiated: username={}, id={}", isUpdate ? "update" : "create", username,
121 user.getId() != null ? user.getId() : "new");
122 }
123
124 try {
125 if (StringUtil.isBlank(user.getSurname())) {
126 user.setSurname(user.getName());
127 }
128
129 ComponentUtil.getAuthenticationManager().insert(user);
130
131 userBhv.insertOrUpdate(user, op -> {
132 op.setRefreshPolicy(Constants.TRUE);
133 });
134
135 if (logger.isInfoEnabled()) {
136 logger.info("User {} completed successfully: username={}, id={}", isUpdate ? "update" : "create", username, user.getId());
137 }
138 } catch (final Exception e) {
139 logger.warn("Failed to {} user: username={}, id={}, error={}", isUpdate ? "update" : "create", username, user.getId(),
140 e.getMessage(), e);
141 throw e;
142 } finally {
143 user.clearOriginalPassword();
144 }
145 }
146
147
148
149
150
151
152
153
154
155 public void changePassword(final String username, final String password) {
156 if (logger.isDebugEnabled()) {
157 logger.debug("Password change initiated for user: username={}", username);
158 }
159
160 try {
161 final boolean changed = ComponentUtil.getAuthenticationManager().changePassword(username, password);
162 if (changed) {
163 userBhv.selectEntity(cb -> cb.query().setName_Equal(username)).ifPresent(entity -> {
164 final String encodedPassword = ComponentUtil.getPasswordHashHelper().encode(password);
165 entity.setPassword(encodedPassword);
166 userBhv.insertOrUpdate(entity, op -> op.setRefreshPolicy(Constants.TRUE));
167
168 if (logger.isInfoEnabled()) {
169 logger.info("Password changed successfully for user: username={}, id={}", username, entity.getId());
170 }
171 }).orElse(() -> {
172 if (logger.isDebugEnabled()) {
173 logger.debug("Failed to change password - user not found: username={}", username);
174 }
175 throw new FessUserNotFoundException(username);
176 });
177 } else {
178 logger.warn("Password change not applied by authentication manager: username={}", username);
179 }
180 } catch (final FessUserNotFoundException e) {
181 throw e;
182 } catch (final Exception e) {
183 logger.warn("Failed to change password for user: username={}, error={}", username, e.getMessage(), e);
184 throw e;
185 }
186 }
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205 public boolean updateStoredPasswordHash(final String username, final String encodedPassword) {
206 return updateStoredPasswordHash(username, null, encodedPassword);
207 }
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223 public boolean updateStoredPasswordHash(final String username, final String expectedCurrentHash, final String newEncodedPassword) {
224 if (StringUtil.isBlank(username) || StringUtil.isBlank(newEncodedPassword)) {
225 return false;
226 }
227
228 try {
229 final OptionalEntity<User> optEntity = userBhv.selectEntity(cb -> cb.query().setName_Equal(username));
230 if (!optEntity.isPresent()) {
231 if (logger.isDebugEnabled()) {
232 logger.debug("User not found for stored password hash update: username={}", username);
233 }
234 return false;
235 }
236 final User entity = optEntity.get();
237 if (expectedCurrentHash != null && !expectedCurrentHash.equals(entity.getPassword())) {
238 if (logger.isDebugEnabled()) {
239 logger.debug("Stored password hash changed concurrently; skipping lazy upgrade: username={}", username);
240 }
241 return false;
242 }
243 entity.setPassword(newEncodedPassword);
244
245
246
247
248
249
250
251
252
253 final Long seqNo = entity.asDocMeta().seqNo();
254 final Long primaryTerm = entity.asDocMeta().primaryTerm();
255 userBhv.update(entity, op -> {
256 op.setRefreshPolicy(Constants.TRUE);
257 if (seqNo != null && seqNo.longValue() >= 0L) {
258 op.setIfSeqNo(seqNo.longValue());
259 }
260 if (primaryTerm != null && primaryTerm.longValue() >= 0L) {
261 op.setIfPrimaryTerm(primaryTerm.longValue());
262 }
263 });
264 if (logger.isDebugEnabled()) {
265 logger.debug("Upgraded stored password hash for user: username={}", username);
266 }
267 return true;
268 } catch (final Exception e) {
269 if (isVersionConflict(e)) {
270
271
272
273 if (logger.isDebugEnabled()) {
274 logger.debug("Version conflict while upgrading password hash; skipping. username={}", username, e);
275 }
276 return false;
277 }
278 logger.warn("Failed to upgrade password hash for user. username={}", username, e);
279 return false;
280 }
281 }
282
283
284
285
286
287
288
289
290
291
292 protected boolean isVersionConflict(final Throwable t) {
293 Throwable cur = t;
294 while (cur != null) {
295 final String name = cur.getClass().getName();
296 if (name.endsWith("VersionConflictEngineException")) {
297 return true;
298 }
299 final String msg = cur.getMessage();
300 if (msg != null && msg.contains("version_conflict_engine_exception")) {
301 return true;
302 }
303 final Throwable next = cur.getCause();
304 if (next == cur) {
305 break;
306 }
307 cur = next;
308 }
309 return false;
310 }
311
312
313
314
315
316
317
318 public void delete(final User user) {
319 final String username = user.getName();
320 final String userId = user.getId();
321
322 if (logger.isDebugEnabled()) {
323 logger.debug("User deletion initiated: username={}, id={}", username, userId);
324 }
325
326 try {
327 ComponentUtil.getAuthenticationManager().delete(user);
328
329 userBhv.delete(user, op -> {
330 op.setRefreshPolicy(Constants.TRUE);
331 });
332
333 if (logger.isInfoEnabled()) {
334 logger.info("User deleted successfully: username={}, id={}", username, userId);
335 }
336 } catch (final Exception e) {
337 logger.warn("Failed to delete user: username={}, id={}, error={}", username, userId, e.getMessage(), e);
338 throw e;
339 }
340 }
341
342
343
344
345
346
347
348
349 protected void setupListCondition(final UserCB cb, final UserPager userPager) {
350 if (userPager.id != null) {
351 cb.query().docMeta().setId_Equal(userPager.id);
352 }
353
354
355
356 cb.query().addOrderBy_Name_Asc();
357
358
359
360 }
361
362
363
364
365
366
367
368 public List<User> getAvailableUserList() {
369 return userBhv.selectList(cb -> {
370 cb.query().matchAll();
371 cb.fetchFirst(fessConfig.getPageUserMaxFetchSizeAsInteger());
372 });
373 }
374
375 }