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

openmrs / openmrs-core / 29867099098

21 Jul 2026 08:37PM UTC coverage: 66.218% (+0.1%) from 66.106%
29867099098

push

github

ibacher
Require Get Patient Programs privilege for by-uuid program lookups (#6318)

* Require Get Patient Programs privilege for by-uuid program lookups

ProgramWorkflowService.getPatientProgramByUuid and getPatientStateByUuid
had no @Authorized annotation. Because AuthorizationAdvice only enforces
when a method declares a privilege, both fell through with no check at
all, letting any authenticated user read program-enrollment PHI by uuid.
Every other read on the service, including getPatientProgram(Integer),
already requires Get Patient Programs; annotate both by-uuid methods to
match.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Require Get Patient Programs privilege for patient program attribute lookup

getPatientProgramAttributeByAttributeName returns patient program
attribute values keyed by patient id, yet lacked an @Authorized check.
Its two sibling attribute readers (getPatientProgramAttributeByUuid and
getPatientProgramByAttributeNameAndValue) already guard on Get Patient
Programs, so this was a missed PHI read of the same class addressed by
GHSA-gqf9-38mg-wgqg.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TpKNfxm3QjwUBDePEtFm2H

* Add authorization tests pinning the by-uuid program privilege checks

The existing tests for getPatientProgramByUuid, getPatientStateByUuid and
getPatientProgramAttributeByAttributeName run as the privileged superuser
and assert only find/null behavior, so they pass even if the
@Authorized(Get Patient Programs) checks added by this branch are removed.
getPatientProgramAttributeByAttributeName had no test at all.

Add a test per method that logs out (dropping Get Patient Programs) and
asserts APIAuthenticationException is thrown for otherwise-valid arguments,
mirroring AuthorizationAdviceTest and AdministrationServiceTest. These fail
if any of the three annotations is removed, pinning the fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cl... (continued)

24392 of 36836 relevant lines covered (66.22%)

0.66 hits per line

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

95.83
/web/src/main/java/org/openmrs/web/filter/initialization/DatabaseDetective.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.web.filter.initialization;
11

12
import java.sql.Connection;
13
import java.sql.DatabaseMetaData;
14
import java.sql.DriverManager;
15
import java.sql.ResultSet;
16
import java.util.Properties;
17

18
import org.openmrs.util.DatabaseUtil;
19

20
public class DatabaseDetective {
1✔
21
        
22
        private static final String CONNECTION_URL = "connection.url";
23
        
24
        private static final String CONNECTION_USERNAME = "connection.username";
25
        
26
        private static final String CONNECTION_PASSWORD = "connection.password";
27
        
28
        /**
29
         * Check whether openmrs database is empty. Having just one non-liquibase table in the given
30
         * database qualifies this as a non-empty database.
31
         *
32
         * @param props the runtime properties
33
         * @return true if the openmrs database is empty or does not exist yet
34
         */
35
        public boolean isDatabaseEmpty(Properties props) {
36
                if (props == null) {
1✔
37
                        return true;
1✔
38
                }
39
                
40
                Connection connection = null;
1✔
41
                
42
                try {
43
                        DatabaseUtil.loadDatabaseDriver(props.getProperty(CONNECTION_URL), null);
1✔
44
                        
45
                        connection = DriverManager.getConnection(props.getProperty(CONNECTION_URL), props
1✔
46
                                .getProperty(CONNECTION_USERNAME), props.getProperty(CONNECTION_PASSWORD));
1✔
47
                        
48
                        DatabaseMetaData dbMetaData = connection.getMetaData();
1✔
49
                        
50
                        String[] types = { "TABLE" };
1✔
51
                        
52
                        //get all tables
53
                        ResultSet tbls = dbMetaData.getTables(null, null, null, types);
1✔
54
                        
55
                        while (tbls.next()) {
1✔
56
                                String tableName = tbls.getString("TABLE_NAME");
1✔
57
                                //if any table exist besides "liquibasechangelog" or "liquibasechangeloglock", return false
58
                                if (!("liquibasechangelog".equals(tableName.toLowerCase()))
1✔
59
                                        && !("liquibasechangeloglock".equals(tableName.toLowerCase()))) {
1✔
60
                                        return false;
1✔
61
                                }
62
                        }
1✔
63
                        return true;
1✔
64
                }
65
                catch (Exception e) {
1✔
66
                        // A configured instance (connection URL set) that we cannot reach is unreachable, not
67
                        // empty. Reporting it empty here would reopen the unauthenticated setup wizard on an
68
                        // already-configured deployment during a database outage, so only a genuinely
69
                        // unconfigured instance (no connection URL) counts as empty.
70
                        String url = props.getProperty(CONNECTION_URL);
1✔
71
                        return url == null || url.trim().isEmpty();
1✔
72
                }
73
                finally {
74
                        if (connection != null) {
1✔
75
                                try {
76
                                        connection.close();
1✔
77
                                }
78
                                catch (Exception e) {
×
79
                                        // ignore; a failure to close must not change the emptiness result
80
                                }
1✔
81
                        }
82
                }
83
        }
84
}
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