• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

openmrs / openmrs-core / 35742310285

22 Sep 2026 02:43PM UTC coverage: 64.906% (-0.01%) from 64.919%
35742310285

push

github

ibacher
TRUNK-6721: Replace Exception-based guard with a thread-local guard in UserDAO (#6588)

44 of 49 new or added lines in 3 files covered. (89.8%)

14 existing lines in 5 files now uncovered.

25079 of 38639 relevant lines covered (64.91%)

0.65 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.24
/api/src/main/java/org/openmrs/api/impl/UserServiceImpl.java
1
/**
2
 * This Source Code Form is subject to the terms of the Mozilla Public License,
3
 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
4
 * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
5
 * the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
6
 *
7
 * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
8
 * graphic logo is a trademark of OpenMRS Inc.
9
 */
10
package org.openmrs.api.impl;
11

12
import java.util.ArrayList;
13
import java.util.Collection;
14
import java.util.Date;
15
import java.util.HashSet;
16
import java.util.List;
17
import java.util.Locale;
18
import java.util.Map;
19
import java.util.Objects;
20
import java.util.Optional;
21
import java.util.Properties;
22
import java.util.Set;
23
import java.util.stream.Collectors;
24

25
import org.apache.commons.lang.RandomStringUtils;
26
import org.apache.commons.lang3.StringUtils;
27
import org.openmrs.Person;
28
import org.openmrs.Privilege;
29
import org.openmrs.Role;
30
import org.openmrs.User;
31
import org.openmrs.annotation.Authorized;
32
import org.openmrs.annotation.Logging;
33
import org.openmrs.api.APIException;
34
import org.openmrs.api.AdministrationService;
35
import org.openmrs.api.CannotDeleteRoleWithChildrenException;
36
import org.openmrs.api.InvalidActivationKeyException;
37
import org.openmrs.api.UserService;
38
import org.openmrs.api.context.Context;
39
import org.openmrs.api.db.DAOException;
40
import org.openmrs.api.db.LoginCredential;
41
import org.openmrs.api.db.UserDAO;
42
import org.openmrs.messagesource.MessageSourceService;
43
import org.openmrs.notification.MessageException;
44
import org.openmrs.patient.impl.LuhnIdentifierValidator;
45
import org.openmrs.util.LocaleUtility;
46
import org.openmrs.util.OpenmrsConstants;
47
import org.openmrs.util.OpenmrsUtil;
48
import org.openmrs.util.PrivilegeConstants;
49
import org.openmrs.util.RoleConstants;
50
import org.openmrs.util.Security;
51
import org.slf4j.Logger;
52
import org.slf4j.LoggerFactory;
53
import org.springframework.cache.annotation.CacheEvict;
54
import org.springframework.transaction.annotation.Transactional;
55

56
/**
57
 * Default implementation of the user service. This class should not be used on its own. The current
58
 * OpenMRS implementation should be fetched from the Context
59
 *
60
 * @see org.openmrs.api.UserService
61
 * @see org.openmrs.api.context.Context
62
 */
63
@Transactional
64
public class UserServiceImpl extends BaseOpenmrsService implements UserService {
65
        
66
        private static final Logger log = LoggerFactory.getLogger(UserServiceImpl.class);
1 ✔
67
        
68
        protected UserDAO dao;
69
        
70
        private static final int MAX_VALID_TIME = 12 * 60 * 60 * 1000; //Period of 12 hours
71
        
72
        private static final int MIN_VALID_TIME = 60 * 1000; //Period of 1 minute
73
        
74
        private static final int DEFAULT_VALID_TIME = 10 * 60 * 1000; //Default time of 10 minute
75
        
76
        public UserServiceImpl() {
1 ✔
77
        }
1 ✔
78
        
79
        public void setUserDAO(UserDAO dao) {
80
                this.dao = dao;
1 ✔
81
        }
1 ✔
82
        
83
        /**
84
         * Thread-scoped permit that marks the current thread as executing inside the user service. The
85
         * {@link UserDAO} implementation consults {@link #isPermitted()} before mutating a user's password
86
         * or login credential, so that those mutations can only happen on a code path that went through
87
         * {@link UserService} rather than straight to the DAO.
88
         * <p>
89
         * The permit is raised and lowered by {@code UserServiceImpl} alone: {@code enter()} and
90
         * {@code exit()} are private, and only a nestmate of {@code UserServiceImpl} can reach them. It is
91
         * re-entrant, so a service call nested inside another one leaves the permit raised until the
92
         * outermost call returns.
93
         * <p>
94
         * Reflection can still forge a permit. That is acceptable: this guards against accidentally routing
95
         * credential changes around the service layer, not against hostile code sharing the JVM, which
96
         * could already reach the DAO by other means.
97
         *
98
         * @since 2.8.10
99
         */
100
        public static final class UserPasswordGuard {
101

102
                private static final ThreadLocal<Integer> DEPTH = new ThreadLocal<>();
1 ✔
103

104
                private UserPasswordGuard() {
105
                }
106

107
                private static void enter() {
108
                        Integer depth = DEPTH.get();
1 ✔
109
                        DEPTH.set(depth == null ? 1 : depth + 1);
1 ✔
110
                }
1 ✔
111

112
                private static void exit() {
113
                        Integer depth = DEPTH.get();
1 ✔
114
                        if (depth == null || depth <= 1) {
1 ✔
115
                                DEPTH.remove();
1 ✔
116
                        } else {
NEW
117
                                DEPTH.set(depth - 1);
×
118
                        }
119
                }
1 ✔
120

121
                /**
122
                 * @return true if the calling thread is currently executing inside the user service
123
                 */
124
                public static boolean isPermitted() {
125
                        return DEPTH.get() != null;
1 ✔
126
                }
127
        }
128

129
        /**
130
         * The service's single entry point to {@link UserDAO#saveUser(User, String)}, raising the permit
131
         * that the DAO requires of anything that can change a password.
132
         *
133
         * @see UserPasswordGuard
134
         */
135
        private User daoSaveUser(User user, String password) {
136
                UserPasswordGuard.enter();
1 ✔
137
                try {
138
                        return dao.saveUser(user, password);
1 ✔
139
                } finally {
140
                        UserPasswordGuard.exit();
1 ✔
141
                }
142
        }
143

144
        /**
145
         * The service's single entry point to {@link UserDAO#changePassword(User, String)}.
146
         *
147
         * @see UserPasswordGuard
148
         */
149
        private void daoChangePassword(User user, String newPassword) {
150
                UserPasswordGuard.enter();
1 ✔
151
                try {
152
                        dao.changePassword(user, newPassword);
1 ✔
153
                } finally {
154
                        UserPasswordGuard.exit();
1 ✔
155
                }
156
        }
1 ✔
157

158
        /**
159
         * The service's single entry point to {@link UserDAO#changeHashedPassword(User, String, String)}.
160
         *
161
         * @see UserPasswordGuard
162
         */
163
        private void daoChangeHashedPassword(User user, String hashedPassword, String salt) {
164
                UserPasswordGuard.enter();
1 ✔
165
                try {
166
                        dao.changeHashedPassword(user, hashedPassword, salt);
1 ✔
167
                } finally {
168
                        UserPasswordGuard.exit();
1 ✔
169
                }
170
        }
1 ✔
171

172
        /**
173
         * @return the validTime for which the password reset activation key will be valid
174
         */
175
        private int getValidTime() {
176
                String validTimeGp = Context.getAdministrationService()
1 ✔
177
                        .getGlobalProperty(OpenmrsConstants.GP_PASSWORD_RESET_VALIDTIME);
1 ✔
178
                final int validTime = StringUtils.isBlank(validTimeGp) ? DEFAULT_VALID_TIME : Integer.parseInt(validTimeGp);
1 ✔
179
                //if valid time is less that a minute or greater than 12hrs reset valid time to 1 minutes else set it to the required time.
180
                return (validTime < MIN_VALID_TIME) || (validTime > MAX_VALID_TIME) ? DEFAULT_VALID_TIME : validTime;
1 ✔
181
        }
182
        
183
        /**
184
         * @see org.openmrs.api.UserService#createUser(org.openmrs.User, java.lang.String)
185
         */
186
        @Override
187
        public User createUser(User user, String password) throws APIException {
188
                if (user.getUserId() != null) {
1 ✔
189
                        throw new APIException("This method can be used for only creating new users");
1 ✔
190
                }
191
                
192
                Context.requirePrivilege(PrivilegeConstants.ADD_USERS);
1 ✔
193
                
194
                checkPrivileges(user);
1 ✔
195
                
196
                // if a password wasn't supplied, throw an error
197
                if (password == null || password.isEmpty()) {
1 ✔
198
                        throw new APIException("User.creating.password.required", (Object[]) null);
×
199
                }
200
                
201
                if (hasDuplicateUsername(user)) {
1 ✔
202
                        throw new DAOException("Username " + user.getUsername() + " or system id " + user.getSystemId()
1 ✔
203
                                + " is already in use.");
204
                }
205
                
206
                // TODO Check required fields for user!!
207
                OpenmrsUtil.validatePassword(user.getUsername(), password, user.getSystemId());
1 ✔
208

209
                return daoSaveUser(user, password);
1 ✔
210
        }
211
        
212
        /**
213
         * @see org.openmrs.api.UserService#getUser(java.lang.Integer)
214
         */
215
        @Override
216
        @Transactional(readOnly = true)
217
        public User getUser(Integer userId) throws APIException {
218
                return dao.getUser(userId);
1 ✔
219
        }
220
        
221
        /**
222
         * @see org.openmrs.api.UserService#getUserByUsername(java.lang.String)
223
         */
224
        @Override
225
        @Transactional(readOnly = true)
226
        public User getUserByUsername(String username) throws APIException {
227
                return dao.getUserByUsername(username);
1 ✔
228
        }
229
        
230
        /**
231
         * @see org.openmrs.api.UserService#hasDuplicateUsername(org.openmrs.User)
232
         */
233
        @Override
234
        @Transactional(readOnly = true)
235
        public boolean hasDuplicateUsername(User user) throws APIException {
236
                return dao.hasDuplicateUsername(user.getUsername(), user.getSystemId(), user.getUserId());
1 ✔
237
        }
238
        
239
        /**
240
         * @see org.openmrs.api.UserService#getUsersByRole(org.openmrs.Role)
241
         */
242
        @Override
243
        @Transactional(readOnly = true)
244
        public List<User> getUsersByRole(Role role) throws APIException {
245
                List<Role> roles = new ArrayList<>();
1 ✔
246
                roles.add(role);
1 ✔
247
                
248
                return Context.getUserService().getUsers(null, roles, false);
1 ✔
249
        }
250
        
251
        /**
252
         * @see org.openmrs.api.UserService#saveUser(org.openmrs.User)
253
         */
254
        @Override
255
        @CacheEvict(value = "userSearchLocales", allEntries = true)
256
        public User saveUser(User user) throws APIException {
257
                if (user.getUserId() == null) {
1 ✔
258
                        throw new APIException("This method can be called only to update existing users");
×
259
                }
260
                
261
                Context.requirePrivilege(PrivilegeConstants.EDIT_USERS);
1 ✔
262
                
263
                checkPrivileges(user);
1 ✔
264
                
265
                if (hasDuplicateUsername(user)) {
1 ✔
266
                        throw new DAOException("Username " + user.getUsername() + " or system id " + user.getSystemId()
×
267
                                + " is already in use.");
268
                }
269

270
                return daoSaveUser(user, null);
1 ✔
271
        }
272
        
273
        public User voidUser(User user, String reason) throws APIException {
274
                return Context.getUserService().retireUser(user, reason);
×
275
        }
276
        
277
        /**
278
         * @see org.openmrs.api.UserService#retireUser(org.openmrs.User, java.lang.String)
279
         */
280
        @Override
281
        public User retireUser(User user, String reason) throws APIException {
282
                user.setRetired(true);
1 ✔
283
                user.setRetireReason(reason);
1 ✔
284
                user.setRetiredBy(Context.getAuthenticatedUser());
1 ✔
285
                user.setDateRetired(new Date());
1 ✔
286
                
287
                return saveUser(user);
1 ✔
288
        }
289
        
290
        public User unvoidUser(User user) throws APIException {
291
                return Context.getUserService().unretireUser(user);
×
292
        }
293
        
294
        /**
295
         * @see org.openmrs.api.UserService#unretireUser(org.openmrs.User)
296
         */
297
        @Override
298
        public User unretireUser(User user) throws APIException {
299
                user.setRetired(false);
1 ✔
300
                user.setRetireReason(null);
1 ✔
301
                user.setRetiredBy(null);
1 ✔
302
                user.setDateRetired(null);
1 ✔
303
                
304
                return saveUser(user);
1 ✔
305
        }
306
        
307
        /**
308
         * @see org.openmrs.api.UserService#getAllUsers()
309
         */
310
        @Override
311
        @Transactional(readOnly = true)
312
        public List<User> getAllUsers() throws APIException {
313
                return dao.getAllUsers();
1 ✔
314
        }
315
        
316
        /**
317
         * @see org.openmrs.api.UserService#getAllPrivileges()
318
         */
319
        @Override
320
        @Transactional(readOnly = true)
321
        public List<Privilege> getAllPrivileges() throws APIException {
322
                return dao.getAllPrivileges();
1 ✔
323
        }
324
        
325
        /**
326
         * @see org.openmrs.api.UserService#getPrivilege(java.lang.String)
327
         */
328
        @Override
329
        @Transactional(readOnly = true)
330
        public Privilege getPrivilege(String p) throws APIException {
331
                return dao.getPrivilege(p);
1 ✔
332
        }
333
        
334
        /**
335
         * @see org.openmrs.api.UserService#purgePrivilege(org.openmrs.Privilege)
336
         */
337
        @Override
338
        @CacheEvict(value = "rolePrivileges", allEntries = true)
339
        public void purgePrivilege(Privilege privilege) throws APIException {
340
                if (OpenmrsUtil.getCorePrivileges().keySet().contains(privilege.getPrivilege())) {
1 ✔
341
                        throw new APIException("Privilege.cannot.delete.core", (Object[]) null);
1 ✔
342
                }
343
                
344
                dao.deletePrivilege(privilege);
1 ✔
345
        }
1 ✔
346
        
347
        /**
348
         * @see org.openmrs.api.UserService#savePrivilege(org.openmrs.Privilege)
349
         */
350
        @Override
351
        @CacheEvict(value = "rolePrivileges", allEntries = true)
352
        public Privilege savePrivilege(Privilege privilege) throws APIException {
353
                return dao.savePrivilege(privilege);
1 ✔
354
        }
355
        
356
        /**
357
         * @see org.openmrs.api.UserService#getAllRoles()
358
         */
359
        @Override
360
        @Transactional(readOnly = true)
361
        public List<Role> getAllRoles() throws APIException {
362
                return dao.getAllRoles();
1 ✔
363
        }
364
        
365
        /**
366
         * @see org.openmrs.api.UserService#getRole(java.lang.String)
367
         */
368
        @Override
369
        @Transactional(readOnly = true)
370
        public Role getRole(String r) throws APIException {
371
                return dao.getRole(r);
1 ✔
372
        }
373
        
374
        /**
375
         * @see org.openmrs.api.UserService#purgeRole(org.openmrs.Role)
376
         */
377
        @Override
378
        @CacheEvict(value = "rolePrivileges", allEntries = true)
379
        public void purgeRole(Role role) throws APIException {
380
                if (role == null || role.getRole() == null) {
1 ✔
381
                        return;
1 ✔
382
                }
383
                
384
                if (OpenmrsUtil.getCoreRoles().keySet().contains(role.getRole())) {
1 ✔
385
                        throw new APIException("Role.cannot.delete.core", (Object[]) null);
1 ✔
386
                }
387
                
388
                if (role.hasChildRoles()) {
1 ✔
389
                        throw new CannotDeleteRoleWithChildrenException();
1 ✔
390
                }
391
                
392
                dao.deleteRole(role);
1 ✔
393
        }
1 ✔
394
        
395
        /**
396
         * @see org.openmrs.api.UserService#saveRole(org.openmrs.Role)
397
         */
398
        @Override
399
        @CacheEvict(value = "rolePrivileges", allEntries = true)
400
        public Role saveRole(Role role) throws APIException {
401
                // make sure one of the parents of this role isn't itself...this would
402
                // cause an infinite loop
403
                if (role.getAllParentRoles().contains(role)) {
1 ✔
404
                        throw new APIException("Role.cannot.inherit.descendant", (Object[]) null);
1 ✔
405
                }
406
                
407
                checkPrivileges(role);
1 ✔
408
                
409
                return dao.saveRole(role);
1 ✔
410
        }
411
        
412
        /**
413
         * @see org.openmrs.api.UserService#changePassword(java.lang.String, java.lang.String)
414
         */
415
        @Override
416
        public void changePassword(String oldPassword, String newPassword) throws APIException {
417
                User user = Context.getAuthenticatedUser();
1 ✔
418
                changePassword(user, oldPassword, newPassword);
1 ✔
419
        }
1 ✔
420
        
421
        /**
422
         * @see org.openmrs.api.UserService#changeHashedPassword(User, String, String)
423
         */
424
        @Override
425
        public void changeHashedPassword(User user, String hashedPassword, String salt) throws APIException {
426
                daoChangeHashedPassword(user, hashedPassword, salt);
1 ✔
427
        }
1 ✔
428
        
429
        /**
430
         * @see org.openmrs.api.UserService#changeQuestionAnswer(User, String, String)
431
         */
432
        @Override
433
        public void changeQuestionAnswer(User u, String question, String answer) throws APIException {
434
                dao.changeQuestionAnswer(u, question, answer);
1 ✔
435
        }
1 ✔
436
        
437
        /**
438
         * @see org.openmrs.api.UserService#changeQuestionAnswer(java.lang.String, java.lang.String,
439
         * java.lang.String)
440
         */
441
        @Override
442
        public void changeQuestionAnswer(String pw, String q, String a) {
443
                dao.changeQuestionAnswer(pw, q, a);
1 ✔
444
        }
1 ✔
445
        
446
        /**
447
         * @see org.openmrs.api.UserService#isSecretAnswer(org.openmrs.User, java.lang.String)
448
         */
449
        @Override
450
        @Transactional(readOnly = true)
451
        public boolean isSecretAnswer(User u, String answer) {
452
                return dao.isSecretAnswer(u, answer);
1 ✔
453
        }
454
        
455
        /**
456
         * @see org.openmrs.api.UserService#getUsersByName(java.lang.String, java.lang.String, boolean)
457
         */
458
        @Override
459
        @Transactional(readOnly = true)
460
        public List<User> getUsersByName(String givenName, String familyName, boolean includeVoided) throws APIException {
461
                return dao.getUsersByName(givenName, familyName, includeVoided);
1 ✔
462
        }
463
        
464
        /**
465
         * @see org.openmrs.api.UserService#getUsersByPerson(org.openmrs.Person, boolean)
466
         */
467
        @Override
468
        @Transactional(readOnly = true)
469
        public List<User> getUsersByPerson(Person person, boolean includeRetired) throws APIException {
470
                return dao.getUsersByPerson(person, includeRetired);
1 ✔
471
        }
472
        
473
        /**
474
         * @see org.openmrs.api.UserService#getUsers(java.lang.String, java.util.List, boolean)
475
         */
476
        @Override
477
        @Transactional(readOnly = true)
478
        public List<User> getUsers(String nameSearch, List<Role> roles, boolean includeVoided) throws APIException {
479
                return Context.getUserService().getUsers(nameSearch, roles, includeVoided, null, null);
1 ✔
480
        }
481
        
482
        /**
483
         * Convenience method to check if the authenticated user has all privileges they are giving out
484
         *
485
         * @param user user that has privileges
486
         */
487
        private void checkPrivileges(User user) {
488
                List<String> requiredPrivs = user.getAllRoles().stream().peek(this::checkSuperUserPrivilege)
1 ✔
489
                        .map(Role::getPrivileges).filter(Objects::nonNull).flatMap(Collection::stream)
1 ✔
490
                        .map(Privilege::getPrivilege).filter(p -> !Context.hasPrivilege(p)).sorted().collect(Collectors.toList());
1 ✔
491
                if (requiredPrivs.size() == 1) {
1 ✔
492
                        throw new APIException("User.you.must.have.privilege", new Object[] { requiredPrivs.get(0) });
1 ✔
493
                } else if (requiredPrivs.size() > 1) {
1 ✔
494
                        throw new APIException("User.you.must.have.privileges", new Object[] { String.join(", ", requiredPrivs) });
1 ✔
495
                }
496
        }
1 ✔
497
        
498
        private void checkSuperUserPrivilege(Role r) {
499
                if (r.getRole().equals(RoleConstants.SUPERUSER)
1 ✔
500
                        && !Context.hasPrivilege(PrivilegeConstants.ASSIGN_SYSTEM_DEVELOPER_ROLE)) {
1 ✔
501
                        throw new APIException("User.you.must.have.role", new Object[] { RoleConstants.SUPERUSER });
1 ✔
502
                }
503
        }
1 ✔
504
        
505
        /**
506
         * @see org.openmrs.api.UserService#setUserProperty(User, String, String)
507
         */
508
        @Override
509
        public User setUserProperty(User user, String key, String value) {
510
                if (user != null) {
1 ✔
511
                        if (!Context.hasPrivilege(PrivilegeConstants.EDIT_USERS) && !user.equals(Context.getAuthenticatedUser())) {
1 ✔
512
                                throw new APIException("you.are.not.authorized.change.properties", new Object[] { user.getUserId() });
×
513
                        }
514
                        
515
                        user.setUserProperty(key, value);
1 ✔
516
                        try {
517
                                Context.addProxyPrivilege(PrivilegeConstants.EDIT_USERS);
1 ✔
518
                                Context.getUserService().saveUser(user);
1 ✔
519
                        }
520
                        finally {
521
                                Context.removeProxyPrivilege(PrivilegeConstants.EDIT_USERS);
1 ✔
522
                        }
523
                }
524
                
525
                return user;
1 ✔
526
        }
527
        
528
        /**
529
         * @see org.openmrs.api.UserService#removeUserProperty(org.openmrs.User, java.lang.String)
530
         */
531
        @Override
532
        public User removeUserProperty(User user, String key) {
533
                if (user != null) {
1 ✔
534
                        
535
                        // if the current user isn't allowed to edit users and
536
                        // the user being edited is not the current user, throw an
537
                        // exception
538
                        if (!Context.hasPrivilege(PrivilegeConstants.EDIT_USERS) && !user.equals(Context.getAuthenticatedUser())) {
1 ✔
539
                                throw new APIException("you.are.not.authorized.change.properties", new Object[] { user.getUserId() });
×
540
                        }
541
                        
542
                        user.removeUserProperty(key);
1 ✔
543
                        
544
                        try {
545
                                Context.addProxyPrivilege(PrivilegeConstants.EDIT_USERS);
1 ✔
546
                                Context.getUserService().saveUser(user);
1 ✔
547
                        }
548
                        finally {
549
                                Context.removeProxyPrivilege(PrivilegeConstants.EDIT_USERS);
1 ✔
550
                        }
551
                }
552
                
553
                return user;
1 ✔
554
        }
555
        
556
        /**
557
         * Generates system ids based on the following algorithm scheme: user_id-check digit
558
         *
559
         * @see org.openmrs.api.UserService#generateSystemId()
560
         */
561
        @Override
562
        @Transactional(readOnly = true)
563
        public String generateSystemId() {
564
                // Hardcoding Luhn algorithm since all existing openmrs user ids have
565
                // had check digits generated this way.
566
                LuhnIdentifierValidator liv = new LuhnIdentifierValidator();
1 ✔
567
                
568
                String systemId;
569
                Integer offset = 0;
1 ✔
570
                do {
571
                        // generate and increment the system id if necessary
572
                        Integer generatedId = dao.generateSystemId() + offset++;
1 ✔
573
                        
574
                        systemId = generatedId.toString();
1 ✔
575
                        
576
                        try {
577
                                systemId = liv.getValidIdentifier(systemId);
1 ✔
578
                        }
579
                        catch (Exception e) {
×
580
                                log.error("error getting check digit", e);
×
581
                                return systemId;
×
582
                        }
1 ✔
583
                        
584
                        // loop until we find a system id that no one has 
585
                } while (dao.hasDuplicateUsername(null, systemId, null));
1 ✔
586
                
587
                return systemId;
1 ✔
588
        }
589
        
590
        /**
591
         * @see org.openmrs.api.UserService#purgeUser(org.openmrs.User)
592
         */
593
        @Override
594
        public void purgeUser(User user) throws APIException {
595
                dao.deleteUser(user);
1 ✔
596
        }
1 ✔
597
        
598
        /**
599
         * @see org.openmrs.api.UserService#purgeUser(org.openmrs.User, boolean)
600
         */
601
        @Override
602
        public void purgeUser(User user, boolean cascade) throws APIException {
603
                if (cascade) {
1 ✔
604
                        throw new APIException("cascade.do.not.think", (Object[]) null);
1 ✔
605
                }
606
                
607
                dao.deleteUser(user);
1 ✔
608
        }
1 ✔
609
        
610
        /**
611
         * Convenience method to check if the authenticated user has all privileges they are giving out
612
         * to the new role
613
         *
614
         * @param role
615
         */
616
        private void checkPrivileges(Role role) {
617
                Optional.ofNullable(role.getPrivileges())
1 ✔
618
                        .map(p -> p.stream().filter(pr -> !Context.hasPrivilege(pr.getPrivilege())).map(Privilege::getPrivilege)
1 ✔
619
                                .distinct().collect(Collectors.joining(", ")))
1 ✔
620
                        .ifPresent(missing -> {
1 ✔
621
                                if (StringUtils.isNotBlank(missing)) {
1 ✔
622
                                        throw new APIException("Role.you.must.have.privileges", new Object[] { missing });
1 ✔
623
                                }
624
                        });
1 ✔
625
        }
1 ✔
626
        
627
        /**
628
         * @see org.openmrs.api.UserService#getPrivilegeByUuid(java.lang.String)
629
         */
630
        @Override
631
        @Transactional(readOnly = true)
632
        public Privilege getPrivilegeByUuid(String uuid) throws APIException {
633
                return dao.getPrivilegeByUuid(uuid);
1 ✔
634
        }
635
        
636
        /**
637
         * @see org.openmrs.api.UserService#getRoleByUuid(java.lang.String)
638
         */
639
        @Override
640
        @Transactional(readOnly = true)
641
        public Role getRoleByUuid(String uuid) throws APIException {
642
                return dao.getRoleByUuid(uuid);
1 ✔
643
        }
644
        
645
        /**
646
         * @see org.openmrs.api.UserService#getUserByUuid(java.lang.String)
647
         */
648
        @Override
649
        @Transactional(readOnly = true)
650
        public User getUserByUuid(String uuid) throws APIException {
651
                return dao.getUserByUuid(uuid);
1 ✔
652
        }
653
        
654
        /**
655
         * @see UserService#getCountOfUsers(String, List, boolean)
656
         */
657
        @Override
658
        @Transactional(readOnly = true)
659
        public Integer getCountOfUsers(String name, List<Role> roles, boolean includeRetired) {
660
                if (name != null) {
×
661
                        name = StringUtils.replace(name, ", ", " ");
×
662
                }
663
                
664
                // if the authenticated role is in the list of searched roles, then all
665
                // persons should be searched
666
                Role authRole = getRole(RoleConstants.AUTHENTICATED);
×
667
                if (roles.contains(authRole)) {
×
668
                        return dao.getCountOfUsers(name, new ArrayList<>(), includeRetired);
×
669
                }
670
                
671
                return dao.getCountOfUsers(name, roles, includeRetired);
×
672
        }
673
        
674
        /**
675
         * @see UserService#getUsers(String, List, boolean, Integer, Integer)
676
         */
677
        @Override
678
        @Transactional(readOnly = true)
679
        public List<User> getUsers(String name, List<Role> roles, boolean includeRetired, Integer start, Integer length)
680
                throws APIException {
681
                if (name != null) {
1 ✔
682
                        name = StringUtils.replace(name, ", ", " ");
1 ✔
683
                }
684
                
685
                if (roles == null) {
1 ✔
686
                        roles = new ArrayList<>();
1 ✔
687
                }
688
                
689
                // if the authenticated role is in the list of searched roles, then all
690
                // persons should be searched
691
                Role authRole = getRole(RoleConstants.AUTHENTICATED);
1 ✔
692
                if (roles.contains(authRole)) {
1 ✔
693
                        return dao.getUsers(name, new ArrayList<>(), includeRetired, start, length);
×
694
                }
695
                
696
                // add the requested roles and all child roles for consideration
697
                Set<Role> allRoles = new HashSet<>();
1 ✔
698
                for (Role r : roles) {
1 ✔
699
                        allRoles.add(r);
1 ✔
700
                        allRoles.addAll(r.getAllChildRoles());
1 ✔
701
                }
1 ✔
702
                
703
                return dao.getUsers(name, new ArrayList<>(allRoles), includeRetired, start, length);
1 ✔
704
        }
705
        
706
        @Override
707
        public User saveUserProperty(String key, String value) {
708
                User user = Context.getAuthenticatedUser();
1 ✔
709
                if (user == null) {
1 ✔
710
                        throw new APIException("no.authenticated.user.found", (Object[]) null);
×
711
                }
712
                user.setUserProperty(key, value);
1 ✔
713
                return daoSaveUser(user, null);
1 ✔
714
        }
715
        
716
        @Override
717
        public User saveUserProperties(Map<String, String> properties) {
718
                User user = Context.getAuthenticatedUser();
1 ✔
719
                if (user == null) {
1 ✔
720
                        throw new APIException("no.authenticated.user.found", (Object[]) null);
×
721
                }
722
                user.getUserProperties().clear();
1 ✔
723
                for (Map.Entry<String, String> entry : properties.entrySet()) {
1 ✔
724
                        user.setUserProperty(entry.getKey(), entry.getValue());
1 ✔
725
                }
1 ✔
726
                return daoSaveUser(user, null);
1 ✔
727
        }
728
        
729
        /**
730
         * @see UserService#changePassword(User, String, String)
731
         */
732
        @Override
733
        @Authorized(PrivilegeConstants.EDIT_USER_PASSWORDS)
734
        @Logging(ignoredArgumentIndexes = { 1, 2 })
735
        public void changePassword(User user, String oldPassword, String newPassword) throws APIException {
736
                if (user.getUserId() == null) {
1 ✔
737
                        throw new APIException("user.must.exist", (Object[]) null);
1 ✔
738
                }
739
                
740
                if (oldPassword == null) {
1 ✔
741
                        if (!Context.hasPrivilege(PrivilegeConstants.EDIT_USER_PASSWORDS)) {
1 ✔
742
                                throw new APIException("null.old.password.privilege.required", (Object[]) null);
×
743
                        }
744
                } else if (!dao.getLoginCredential(user).checkPassword(oldPassword)) {
1 ✔
745
                        throw new APIException("old.password.not.correct", (Object[]) null);
×
746
                        
747
                } else if (oldPassword.equals(newPassword)) {
1 ✔
748
                        throw new APIException("new.password.equal.to.old", (Object[]) null);
×
749
                }
750
                
751
                if (("admin".equals(user.getSystemId()) || "admin".equals(user.getUsername())) && Boolean.parseBoolean(
1 ✔
752
                        Context.getRuntimeProperties().getProperty(ADMIN_PASSWORD_LOCKED_PROPERTY, "false"))) {
1 ✔
753
                        throw new APIException("admin.password.is.locked");
1 ✔
754
                }
755
                
756
                updatePassword(user, newPassword);
1 ✔
757
        }
1 ✔
758
        
759
        @Override
760
        @Logging(ignoredArgumentIndexes = { 1 })
761
        public void changePassword(User user, String newPassword) {
762
                Context.getUserService().changePassword(user, null, newPassword);
1 ✔
763
        }
1 ✔
764
        
765
        @Override
766
        @Logging(ignoredArgumentIndexes = { 1, 2 })
767
        public void changePasswordUsingSecretAnswer(String secretAnswer, String pw) throws APIException {
768
                User user = Context.getAuthenticatedUser();
1 ✔
769
                
770
                if (!isSecretAnswer(user, secretAnswer)) {
1 ✔
771
                        throw new APIException("secret.answer.not.correct", (Object[]) null);
1 ✔
772
                }
773
                
774
                updatePassword(user, pw);
1 ✔
775
        }
1 ✔
776

777
        private void updatePassword(User user, String newPassword) {
778
                OpenmrsUtil.validatePassword(user.getUsername(), newPassword, user.getSystemId());
1 ✔
779
                daoChangePassword(user, newPassword);
1 ✔
780
        }
1 ✔
781
        
782
        @Override
783
        public String getSecretQuestion(User user) throws APIException {
784
                if (user.getUserId() != null) {
×
785
                        LoginCredential loginCredential = dao.getLoginCredential(user);
×
786
                        return loginCredential.getSecretQuestion();
×
787
                } else {
788
                        return null;
×
789
                }
790
        }
791
        
792
        /**
793
         * @see org.openmrs.api.UserService#getUserByUsernameOrEmail(java.lang.String)
794
         */
795
        @Override
796
        @Transactional(readOnly = true)
797
        public User getUserByUsernameOrEmail(String usernameOrEmail) {
798
                if (StringUtils.isNotBlank(usernameOrEmail)) {
1 ✔
799
                        User user = dao.getUserByEmail(usernameOrEmail);
1 ✔
800
                        if (user == null) {
1 ✔
801
                                return getUserByUsername(usernameOrEmail);
1 ✔
802
                        }
803
                        return user;
1 ✔
804
                }
805
                throw new APIException("error.usernameOrEmail.notNullOrBlank", (Object[]) null);
1 ✔
806
        }
807
        
808
        /**
809
         * @see org.openmrs.api.UserService#getUserByActivationKey(java.lang.String)
810
         */
811
        @Override
812
        @Transactional(readOnly = true)
813
        public User getUserByActivationKey(String activationKey) {
814
                LoginCredential loginCred = dao.getLoginCredentialByActivationKey(activationKey);
1 ✔
815
                if (loginCred != null) {
1 ✔
816
                        String[] credTokens = loginCred.getActivationKey().split(":");
1 ✔
817
                        if (System.currentTimeMillis() <= Long.parseLong(credTokens[1])) {
1 ✔
818
                                return getUser(loginCred.getUserId());
1 ✔
819
                        }
820
                }
821
                return null;
1 ✔
822
        }
823
        
824
        /**
825
         * @throws APIException
826
         * @throws MessageException
827
         * @see org.openmrs.api.UserService#setUserActivationKey(org.openmrs.User)
828
         */
829
        @Override
830
        public User setUserActivationKey(User user) throws MessageException {
831
                String token = RandomStringUtils.randomAlphanumeric(20);
1 ✔
832
                long time = System.currentTimeMillis() + getValidTime();
1 ✔
833
                String hashedKey = Security.encodeString(token);
1 ✔
834
                String activationKey = hashedKey + ":" + time;
1 ✔
835
                LoginCredential credentials = dao.getLoginCredential(user);
1 ✔
836
                credentials.setActivationKey(activationKey);
1 ✔
837
                dao.setUserActivationKey(credentials);
1 ✔
838
                
839
                MessageSourceService messages = Context.getMessageSourceService();
1 ✔
840
                AdministrationService adminService = Context.getAdministrationService();
1 ✔
841
                Locale locale = getDefaultLocaleForUser(user);
1 ✔
842
                
843
                //                Delete this method call when removing {@link OpenmrsConstants#GP_HOST_URL}
844
                copyHostURLGlobalPropertyToPasswordResetGlobalProperty(adminService);
1 ✔
845
                
846
                String link = adminService.getGlobalProperty(OpenmrsConstants.GP_PASSWORD_RESET_URL)
1 ✔
847
                        .replace("{activationKey}", token);
1 ✔
848
                
849
                Properties mailProperties = Context.getMailProperties();
1 ✔
850
                
851
                String sender = mailProperties.getProperty("mail.from");
1 ✔
852
                
853
                String subject = messages.getMessage("mail.passwordreset.subject", null, locale);
1 ✔
854
                
855
                String msg = messages.getMessage("mail.passwordreset.content", null, locale)
1 ✔
856
                        .replace("{name}", user.getUsername())
1 ✔
857
                        .replace("{link}", link)
1 ✔
858
                        .replace("{time}", String.valueOf(getValidTime() / 60000));
1 ✔
859
                
860
                Context.getMessageService().sendMessage(user.getEmail(), sender, subject, msg);
×
861
                
862
                return user;
×
863
        }
864
        
865
        /**
866
         * Delete this method when deleting {@link OpenmrsConstants#GP_HOST_URL}
867
         */
868
        private void copyHostURLGlobalPropertyToPasswordResetGlobalProperty(AdministrationService adminService) {
869
                String hostURLGP = adminService.getGlobalProperty(OpenmrsConstants.GP_HOST_URL);
1 ✔
870
                String passwordResetGP = adminService.getGlobalProperty(OpenmrsConstants.GP_PASSWORD_RESET_URL);
1 ✔
871
                if (StringUtils.isNotBlank(hostURLGP) && StringUtils.isBlank(passwordResetGP)) {
1 ✔
872
                        adminService.setGlobalProperty(OpenmrsConstants.GP_PASSWORD_RESET_URL, hostURLGP);
×
873
                }
874
        }
1 ✔
875
        
876
        /**
877
         * @see UserService#getDefaultLocaleForUser(User)
878
         */
879
        @Override
880
        public Locale getDefaultLocaleForUser(User user) {
881
                Locale locale = null;
1 ✔
882
                if (user != null) {
1 ✔
883
                        try {
884
                                String preferredLocale = user.getUserProperty(OpenmrsConstants.USER_PROPERTY_DEFAULT_LOCALE);
1 ✔
885
                                if (StringUtils.isNotBlank(preferredLocale)) {
1 ✔
886
                                        locale = LocaleUtility.fromSpecification(preferredLocale);
1 ✔
887
                                }
888
                        }
889
                        catch (Exception e) {
×
890
                                log.warn("Unable to parse user locale into a Locale", e);
×
891
                        }
1 ✔
892
                }
893
                if (locale == null) {
1 ✔
894
                        locale = Context.getLocale();
1 ✔
895
                }
896
                return locale;
1 ✔
897
        }
898
        
899
        /**
900
         * @see org.openmrs.api.UserService#changePasswordUsingActivationKey(String, String);
901
         */
902
        @Override
903
        public void changePasswordUsingActivationKey(String activationKey, String newPassword) {
904
                User user = getUserByActivationKey(activationKey);
1 ✔
905
                if (user == null) {
1 ✔
906
                        throw new InvalidActivationKeyException("activation.key.not.correct");
1 ✔
907
                }
908
                
909
                updatePassword(user, newPassword);
1 ✔
910
        }
1 ✔
911

912
        /**
913
         * @see org.openmrs.api.UserService#getLastLoginTime(User)
914
         */
915
        public String getLastLoginTime(User user) {
916
                return dao.getLastLoginTime(user);
1 ✔
917
        }
918
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc