-
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
d5c079c
e9b38a2
3714b6d
251cf6f
4ef61c2
65c4f93
ac35624
4f58051
7b4b14f
865ede1
c4cfb5d
3e31eda
700e2e2
e1bde6e
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); | ||
|
Comment on lines
+182
to
+186
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 Remove the duplicate stale account-state write. Successful authentication already clears and persists failed-attempt state in
π Affects 3 files
π€ Prompt for AI Agents |
||
| 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 |
|---|---|---|
|
|
@@ -22,25 +22,26 @@ | |
| /* | ||
| * AMRIT β Accessible Medical Records via Integrated Technology | ||
| */ | ||
| package com.iemr.common.data.userToken; | ||
|
Check warning on line 25 in src/main/java/com/iemr/common/data/userToken/UserFcmTokenData.java
|
||
|
|
||
| import jakarta.persistence.Column; | ||
| import jakarta.persistence.Entity; | ||
| import jakarta.persistence.Id; | ||
| import jakarta.persistence.Table; | ||
| import jakarta.persistence.*; | ||
| import lombok.Data; | ||
|
|
||
| import java.sql.Timestamp; | ||
|
|
||
| import static jakarta.persistence.GenerationType.IDENTITY; | ||
|
|
||
| @Entity | ||
| @Table(name = "user_tokens", schema = "db_iemr") | ||
| @Table(name = "user_fcm_tokens", schema = "db_iemr") | ||
| @Data | ||
| public class UserTokenData { | ||
| public class UserFcmTokenData { | ||
| @Id | ||
| @GeneratedValue(strategy = IDENTITY) | ||
| private int id; | ||
| @Column(name = "user_id") | ||
| Integer userId; | ||
| private Integer userId; | ||
| @Column(name = "token") | ||
| String token; | ||
| private String token; | ||
| @Column(name = "updated_at") | ||
| Timestamp updatedAt; | ||
| private Timestamp updatedAt; | ||
|
Comment on lines
34
to
+46
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. ποΈ Data Integrity & Integration | π Major | β‘ Quick win π§© Analysis chainπ Script executed: #!/bin/bash
# Look for migration scripts referencing the renamed table
rg -n "user_fcm_tokens|user_tokens" -g '*.sql' -g '*.xml' -g '*.yml'
fd -i changelogRepository: PSMRI/Common-API Length of output: 154 π Script executed: #!/bin/bash
set -u
echo "== files matching migration-related names =="
git ls-files | rg -n -i 'liquibase|flyway|migration|changelog|db|schema|sql$|yaml|yml' | head -200
echo
echo "== tracking all references to token tables across tracked files =="
rg -n -i "user_fcm_tokens|user_tokens|firebase|fcm" . \
-g '!**/target/**' -g '!**/build/**' -g '!**/.git/**' -g '!**/node_modules/**' \
| head -200
echo
echo "== candidate data files =="
fd -i 'UserFcmTokenData.java|FirebaseNotificationService.java' .Repository: PSMRI/Common-API Length of output: 17601 π Script executed: #!/bin/bash
set -u
echo "== UserFcmTokenData =="
cat -n src/main/java/com/iemr/common/data/userToken/UserFcmTokenData.java
echo
echo "== UserFcmTokenRepo =="
cat -n src/main/java/com/iemr/common/repo/userToken/UserFcmTokenRepo.java
echo
echo "== FirebaseNotificationService relevant method =="
sed -n '120,165p' src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java | cat -n
echo
echo "== targeted table references in all tracked files =="
python3 - <<'PY'
import subprocess
result=subprocess.run(["git","ls-files","*"],capture_output=True,text=True,check=True)
terms=("user_fcm_tokens","user_tokens","firebase","fcm")
for line in result.stdout.splitlines():
if not line.startswith(".") or line.startswith(".github"):
try:
data=open(line,errors="ignore").read()
except Exception:
continue
if any(t in data for t in terms):
if "user_fcm_tokens" in data or "user_tokens" in data:
print(f"\n--- {line} ---")
for i,l in enumerate(data.splitlines(),1):
if "user_fcm_tokens" in l or "user_tokens" in l:
print(f"{i}: {l}")
PYRepository: PSMRI/Common-API Length of output: 5484 Add a migration for the new FCM token schema.
Also add a DB-unique constraint on π€ Prompt for AI Agents |
||
| } | ||
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.
π©Ί Stability & Availability | π Major | β‘ Quick win
π§© Analysis chain
π Script executed:
Repository: PSMRI/Common-API
Length of output: 20758
π Script executed:
Repository: PSMRI/Common-API
Length of output: 1847
Make Firebase config optional at startup and remove the duplicated
@Values. BothFirebaseMessagingConfigandFirebaseNotificationServicebindfirebase.enabled/firebase.credential-filewithout defaults; if these env-substituted properties are absent, Spring fails before Firebase can be used or disabled. Add explicit defaults (@Value("${firebase.enabled:false}")/@Value("${firebase.credential-file:}")), let only the config bean own the config, and remove the duplicated fields and misleading per-request βInitializing Firebaseβ log from the service.π Affects 2 files
src/main/java/com/iemr/common/config/firebase/FirebaseMessagingConfig.java#L24-L36(this comment)src/main/java/com/iemr/common/service/firebaseNotification/FirebaseNotificationService.java#L70-L82π€ Prompt for AI Agents