-
Notifications
You must be signed in to change notification settings - Fork 56
Release 3.8.2 to main #438
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
3c5b5b8
eb9de55
2cd890c
bc54def
3e0bbb7
cbb1817
8b548d3
a8102b1
2c23d93
fe3336a
5424887
a6a6d3c
758bf36
be92d7c
fc1deba
e04714b
fee73b3
818257c
a27db85
38e4c02
73caab7
d08403f
5a34772
c9a2694
c3013e0
16dc5af
8743f34
865c266
89d1da2
d2efa54
01a5f5f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| /* | ||
| * AMRIT β Accessible Medical Records via Integrated Technology | ||
| * Integrated EHR (Electronic Health Records) Solution | ||
| * | ||
| * Copyright (C) "Piramal Swasthya Management and Research Institute" | ||
| * | ||
| * This file is part of AMRIT. | ||
| * | ||
| * This program is free software: you can redistribute it and/or modify | ||
| * it under the terms of the GNU General Public License as published by | ||
| * the Free Software Foundation, either version 3 of the License, or | ||
| * (at your option) any later version. | ||
| * | ||
| * This program is distributed in the hope that it will be useful, | ||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| * GNU General Public License for more details. | ||
| * | ||
| * You should have received a copy of the GNU General Public License | ||
| * along with this program. If not, see https://www.gnu.org/licenses/. | ||
| */ | ||
| package com.iemr.common.controller.connect; | ||
|
|
||
| import java.util.LinkedHashMap; | ||
| import java.util.Map; | ||
|
|
||
| import org.springframework.beans.factory.annotation.Value; | ||
| import org.springframework.http.MediaType; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.bind.annotation.GetMapping; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
|
|
||
| import com.iemr.common.utils.NetworkUtil; | ||
|
|
||
| import io.swagger.v3.oas.annotations.Operation; | ||
|
|
||
| /** | ||
| * Exposes the server's LAN address so a mobile device on the same wifi | ||
| * network can connect to this API. | ||
| */ | ||
| @RestController | ||
| @RequestMapping(value = "/public/connect") | ||
| public class ConnectController { | ||
|
|
||
| @Value("${server.port:8080}") | ||
| private int serverPort; | ||
|
|
||
| @Operation(summary = "Get the server's LAN IP address and port") | ||
| @GetMapping(value = "/info", produces = MediaType.APPLICATION_JSON_VALUE) | ||
| public ResponseEntity<Map<String, Object>> getConnectInfo() { | ||
| Map<String, Object> response = new LinkedHashMap<>(); | ||
| response.put("ip", NetworkUtil.getLanIPAddress()); | ||
| response.put("port", serverPort); | ||
| return ResponseEntity.ok(response); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -82,6 +82,7 @@ | |
| private static final String USER_ID_FIELD = "userId"; | ||
| private final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); | ||
| private InputMapper inputMapper = new InputMapper(); | ||
| private static final Set<String> CONCURRENT_SESSION_EXEMPT_ROLES = Set.of("provideradmin", "superadmin"); | ||
|
|
||
| // @Value("${captcha.enable-captcha}") | ||
| private boolean enableCaptcha =false; | ||
|
|
@@ -172,7 +173,17 @@ | |
| } | ||
|
|
||
| String decryptPassword = aesUtil.decrypt("Piramal12Piramal", m_User.getPassword()); | ||
| List<User> mUser = iemrAdminUserServiceImpl.userAuthenticate(m_User.getUserName(), decryptPassword); | ||
|
|
||
|
|
||
| List<User> mUser = iemrAdminUserServiceImpl | ||
| .userAuthenticate(m_User.getUserName(), decryptPassword); | ||
|
|
||
|
|
||
| User loggedInUser = mUser.get(0); | ||
|
|
||
| loggedInUser.setFailedAttempt(0); | ||
|
|
||
| iemrAdminUserServiceImpl.save(loggedInUser); | ||
| JSONObject resMap = new JSONObject(); | ||
| JSONObject serviceRoleMultiMap = new JSONObject(); | ||
| JSONObject serviceRoleMap = new JSONObject(); | ||
|
|
@@ -181,11 +192,22 @@ | |
| if (m_User.getUserName() != null | ||
| && (m_User.getDoLogout() == null || !m_User.getDoLogout()) | ||
| && (m_User.getWithCredentials() != null && m_User.getWithCredentials())) { | ||
| String tokenFromRedis = getConcurrentCheckSessionObjectAgainstUser( | ||
| m_User.getUserName().trim().toLowerCase()); | ||
| if (tokenFromRedis != null) { | ||
| throw new IEMRException( | ||
| "You are already logged in,please confirm to logout from other device and login again"); | ||
| String userRole = ""; | ||
| if (mUser.size() == 1 && mUser.get(0).getM_UserServiceRoleMapping() != null) { | ||
| for (UserServiceRoleMapping usrm : mUser.get(0).getM_UserServiceRoleMapping()) { | ||
| if (usrm.getM_Role() != null && usrm.getM_Role().getRoleName() != null) { | ||
| userRole = usrm.getM_Role().getRoleName(); | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| if (!CONCURRENT_SESSION_EXEMPT_ROLES.contains(userRole.trim().toLowerCase())) { | ||
| String tokenFromRedis = getConcurrentCheckSessionObjectAgainstUser( | ||
| m_User.getUserName().trim().toLowerCase()); | ||
| if (tokenFromRedis != null) { | ||
| throw new IEMRException( | ||
| "You are already logged in,please confirm to logout from other device and login again"); | ||
| } | ||
|
Comment on lines
+195
to
+210
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π Security & Privacy | π Major | β‘ Quick win π§© Analysis chainπ Script executed: #!/bin/bash
set -euo pipefail
rg -n -C 5 'getUserServiceRoleMapping\s*\(|userExitsCheck\s*\(|CONCURRENT_SESSION_EXEMPT_ROLES' \
src/main/java/com/iemr/common
rg -n -C 5 'IEMRAdminController|concurrent.*session|provideradmin|superadmin' \
src/test/javaRepository: PSMRI/Common-API Length of output: 38446 π Script executed: #!/bin/bash
set -euo pipefail
sed -n '175,220p' src/main/java/com/iemr/common/controller/users/IEMRAdminController.java
printf '\n----\n'
sed -n '410,470p' src/main/java/com/iemr/common/controller/users/IEMRAdminController.java
printf '\n----\n'
sed -n '700,760p' src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java
printf '\n----\n'
sed -n '1328,1395p' src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.javaRepository: PSMRI/Common-API Length of output: 10532 π Script executed: #!/bin/bash
set -euo pipefail
rg -n -C 4 'findByUserName\s*\(|userExitsCheck\s*\(' src/main/java/com/iemr/common
printf '\n----\n'
rg -n -C 6 'interface .*RepositoryCustom|class .*RepositoryCustom|findByUserName' src/main/java/com/iemr/common
printf '\n----\n'
sed -n '1,220p' src/main/java/com/iemr/common/repository/users/IEMRUserRepositoryCustom.javaRepository: PSMRI/Common-API Length of output: 39428 Use the complete role mappings for concurrent-session checks.
Define whether an exempt role should match any mapping or require all mappings to be exempt, then add a multi-role test. π Affects 1 file
π€ Prompt for AI Agents |
||
| } | ||
| } else if (m_User.getUserName() != null && m_User.getDoLogout() != null && m_User.getDoLogout() == true) { | ||
| deleteSessionObject(m_User.getUserName().trim().toLowerCase()); | ||
|
|
@@ -254,7 +276,6 @@ | |
| // Facility data for ALL users - common pattern, empty if not applicable | ||
| try { | ||
| if (mUser.size() == 1) { | ||
| User loggedInUser = mUser.get(0); | ||
| String userRoleName = ""; | ||
| if (loggedInUser.getM_UserServiceRoleMapping() != null) { | ||
| for (UserServiceRoleMapping usrm : loggedInUser.getM_UserServiceRoleMapping()) { | ||
|
|
@@ -413,16 +434,28 @@ | |
| deleteSessionObjectByGettingSessionDetails(previousTokenFromRedis); | ||
| sessionObject.deleteSessionObject(previousTokenFromRedis); | ||
|
|
||
| // Denylist the active JWT so System 1's requests are immediately rejected | ||
| String usernameKey = mUsers.get(0).getUserName().trim().toLowerCase(); | ||
| String jtiData = stringRedisTemplate.opsForValue().get("jti:" + usernameKey); | ||
| if (jtiData != null) { | ||
| String[] parts = jtiData.split("\\|", 2); | ||
| tokenDenylist.addTokenToDenylist(parts[0], jwtUtil.getAccessTokenExpiration()); | ||
| if (parts.length > 1) { | ||
| redisTemplate.delete("user_" + parts[1]); | ||
| String userRole = ""; | ||
| if (mUsers.get(0).getM_UserServiceRoleMapping() != null) { | ||
| for (UserServiceRoleMapping usrm : mUsers.get(0).getM_UserServiceRoleMapping()) { | ||
| if (usrm.getM_Role() != null && usrm.getM_Role().getRoleName() != null) { | ||
| userRole = usrm.getM_Role().getRoleName(); | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| if (!CONCURRENT_SESSION_EXEMPT_ROLES.contains(userRole.trim().toLowerCase())) { | ||
| // Denylist the active JWT so the first system's requests are immediately rejected | ||
| String usernameKey = mUsers.get(0).getUserName().trim().toLowerCase(); | ||
| String jtiData = stringRedisTemplate.opsForValue().get("jti:" + usernameKey); | ||
| if (jtiData != null) { | ||
| String[] parts = jtiData.split("\\|", 2); | ||
| String jti = parts[0]; | ||
| tokenDenylist.addTokenToDenylist(jti, jwtUtil.getAccessTokenExpiration()); | ||
| if (parts.length > 1) { | ||
| redisTemplate.delete("user_" + parts[1]); | ||
| } | ||
| stringRedisTemplate.delete("jti:" + usernameKey); | ||
| } | ||
| stringRedisTemplate.delete("jti:" + usernameKey); | ||
| } | ||
|
|
||
| response.setResponse("User successfully logged out"); | ||
|
|
@@ -537,11 +570,13 @@ | |
| String refreshToken = null; | ||
| boolean isMobile = false; | ||
| if (m_User.getUserName() != null && (m_User.getDoLogout() == null || m_User.getDoLogout() == false)) { | ||
| String tokenFromRedis = getConcurrentCheckSessionObjectAgainstUser( | ||
| m_User.getUserName().trim().toLowerCase()); | ||
| if (tokenFromRedis != null) { | ||
| throw new IEMRException( | ||
| "You are already logged in,please confirm to logout from other device and login again"); | ||
| if (!CONCURRENT_SESSION_EXEMPT_ROLES.contains(m_User.getUserName().trim().toLowerCase())) { | ||
| String tokenFromRedis = getConcurrentCheckSessionObjectAgainstUser( | ||
| m_User.getUserName().trim().toLowerCase()); | ||
| if (tokenFromRedis != null) { | ||
| throw new IEMRException( | ||
| "You are already logged in,please confirm to logout from other device and login again"); | ||
| } | ||
| } | ||
| } else if (m_User.getUserName() != null && m_User.getDoLogout() != null && m_User.getDoLogout() == true) { | ||
| deleteSessionObject(m_User.getUserName().trim().toLowerCase()); | ||
|
|
@@ -1000,6 +1035,13 @@ | |
| try { | ||
| deleteSessionObjectByGettingSessionDetails(request.getHeader("Authorization")); | ||
| sessionObject.deleteSessionObject(request.getHeader("Authorization")); | ||
| try { | ||
|
Check warning on line 1038 in src/main/java/com/iemr/common/controller/users/IEMRAdminController.java
|
||
| stringRedisTemplate.delete("camp:vanID"); | ||
| stringRedisTemplate.delete("camp:parkingPlaceID"); | ||
| logger.info("Camp config cleared from Redis on MMU logout"); | ||
| } catch (Exception redisEx) { | ||
| logger.warn("Failed to clear camp Redis keys on logout: {}", redisEx.getMessage()); | ||
| } | ||
| response.setResponse("Success"); | ||
| } catch (Exception e) { | ||
| response.setError(e); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -52,6 +52,12 @@ | |
| private String parkingPlaceName; | ||
| private Integer servicePointID; | ||
| private String servicePointName; | ||
| private Double gpsLatitude; | ||
| private Double gpsLongitude; | ||
| private String digipin; | ||
| private Long gpsTimestamp; | ||
| private Boolean isGpsUnavailable; | ||
| private String gpsUnavailableReason; | ||
|
Comment on lines
+55
to
+60
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π― Functional Correctness | π‘ Minor | β‘ Quick win π§© Analysis chainπ Script executed: #!/bin/bash
set -euo pipefail
echo "== Address.java =="
sed -n '1,220p' src/main/java/com/iemr/common/dto/identity/Address.java
echo
echo "== BenCompleteDetailMapperDecorator.java =="
sed -n '1,260p' src/main/java/com/iemr/common/mapper/BenCompleteDetailMapperDecorator.java
echo
echo "== Search for isGpsUnavailable usages =="
rg -n "isGpsUnavailable|getIsGpsUnavailable|setIsGpsUnavailable|GpsUnavailableReason|gpsUnavailable" src/main/javaRepository: PSMRI/Common-API Length of output: 16013 π Script executed: #!/bin/bash
set -euo pipefail
echo "== BeneficiaryDemographicsModel excerpt =="
sed -n '150,220p' src/main/java/com/iemr/common/model/beneficiary/BeneficiaryDemographicsModel.java
echo
echo "== All direct reads of getIsGpsUnavailable =="
rg -n "getIsGpsUnavailable\\(" src/main/java
echo
echo "== All direct Boolean checks on isGpsUnavailable =="
rg -n "isGpsUnavailable|GpsUnavailable" src/main/java/com/iemr/common | sed -n '1,200p'Repository: PSMRI/Common-API Length of output: 4570 Preserve the
π Affects 2 files
π€ Prompt for AI Agents |
||
|
|
||
| public static Address bendemographicsAddressMapper(BeneficiaryDemographicsModel i_bendemographics) { | ||
| Address address = new Address(); | ||
|
|
@@ -87,6 +93,12 @@ public static Address bendemographicsAddressMapper(BeneficiaryDemographicsModel | |
| address.setSubDistrictId(i_bendemographics.getBlockID()); | ||
| address.setVillageId(i_bendemographics.getDistrictBranchID()); | ||
| address.setPinCode(i_bendemographics.getPinCode()); | ||
| address.setGpsLatitude(i_bendemographics.getLatitude()); | ||
| address.setGpsLongitude(i_bendemographics.getLongitude()); | ||
| address.setDigipin(i_bendemographics.getDigipin()); | ||
| address.setGpsTimestamp(i_bendemographics.getGpsTimestamp()); | ||
| address.setIsGpsUnavailable(i_bendemographics.getIsGpsUnavailable()); | ||
| address.setGpsUnavailableReason(i_bendemographics.getGpsUnavailableReason()); | ||
|
|
||
| return address; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -160,6 +160,9 @@ public FormResponseDTO getStructuredFormByFormId(String formId, String lang, Str | |
| } else if ("en".equalsIgnoreCase(lang)) { | ||
| translatedLabel = label.getEnglish(); | ||
|
|
||
| }else if ("bn".equalsIgnoreCase(lang)) { | ||
| translatedLabel = label.getBengaliTranslation(); | ||
|
|
||
|
Comment on lines
+163
to
+165
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π― Functional Correctness | π Major | β‘ Quick win Preserve a fallback when Bengali text is unavailable. These branches assign nullable Bengali values directly, so existing or partially translated records can produce null field labels, placeholders, and option labels. Use Bengali only when it is nonblank; otherwise retain the field/placeholder default and the English option label. Also applies to: 177-179, 212-213 π€ Prompt for AI Agents |
||
| } | ||
| } | ||
|
|
||
|
|
@@ -171,6 +174,9 @@ public FormResponseDTO getStructuredFormByFormId(String formId, String lang, Str | |
| } else if ("en".equalsIgnoreCase(lang)) { | ||
| translatedPlaceHolder = placeHolder.getEnglish(); | ||
|
|
||
| } else if ("bn".equalsIgnoreCase(lang)) { | ||
| translatedPlaceHolder = placeHolder.getBengaliTranslation(); | ||
|
|
||
| } | ||
| } | ||
|
|
||
|
|
@@ -203,7 +209,8 @@ public FormResponseDTO getStructuredFormByFormId(String formId, String lang, Str | |
| map.put("value", opt.getValue()); | ||
| if ("hi".equalsIgnoreCase(lang)) map.put("label", opt.getLabelHi()); | ||
| else if ("as".equalsIgnoreCase(lang)) map.put("label", opt.getLabelAs()); | ||
| else map.put("label", opt.getLabelEn()); | ||
| else if("en".equals(lang)) map.put("label", opt.getLabelEn()); | ||
| else if("bn".equals(lang)) map.put("label", opt.getLabelBn()); | ||
|
Comment on lines
+212
to
+213
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π― Functional Correctness | π‘ Minor | β‘ Quick win Use case-insensitive matching for option languages. The field and placeholder paths use π€ Prompt for AI Agents |
||
| return map; | ||
| }) | ||
| .collect(Collectors.toList()); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π Security & Privacy | π Major | β‘ Quick win
Remove the duplicate stale account-state write. Successful authentication already clears and persists failed-attempt state in
IEMRAdminUserServiceImpl; this later save can overwrite a concurrent failed-login/lock update.src/main/java/com/iemr/common/controller/users/IEMRAdminController.java#L182-L186: do not reset and save the returned user again.src/main/java/com/iemr/common/service/users/IEMRAdminUserService.java#L136-L136: remove the rawsave(User)contract.src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java#L1388-L1391: remove the corresponding repository delegation.π Affects 3 files
src/main/java/com/iemr/common/controller/users/IEMRAdminController.java#L182-L186(this comment)src/main/java/com/iemr/common/service/users/IEMRAdminUserService.java#L136-L136src/main/java/com/iemr/common/service/users/IEMRAdminUserServiceImpl.java#L1388-L1391π€ Prompt for AI Agents