Machine Installation id based Oauth 2

This commit is contained in:
2026-07-26 15:19:27 +05:30
parent dcb40473da
commit a4daf7e204
365 changed files with 23720 additions and 966 deletions

View File

@@ -23,6 +23,11 @@
</properties>
<dependencies>
<dependency>
<groupId>com.cygnus</groupId>
<artifactId>cygnus-cloud-client</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
@@ -99,6 +104,11 @@
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>

View File

@@ -0,0 +1,20 @@
package matrix.nimble.cloud.identity;
public class CloudAuthenticationException extends RuntimeException {
public enum Reason {
INVALID_CREDENTIALS,
SERVICE_UNAVAILABLE
}
private final Reason reason;
public CloudAuthenticationException(Reason reason, String message, Throwable cause) {
super(message, cause);
this.reason = reason;
}
public Reason reason() {
return reason;
}
}

View File

@@ -0,0 +1,53 @@
package matrix.nimble.cloud.identity;
import com.cygnus.client.CloudClientProperties;
import com.cygnus.client.CloudIdentityClient;
import java.time.Duration;
import matrix.nimble.model.Session;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClientResponseException;
@Service
public class CloudAuthenticationGateway {
private final CloudIdentityClient client;
private final CloudClientProperties properties;
private final CloudSessionMapper sessionMapper;
public CloudAuthenticationGateway(
CloudIdentityClient client,
CloudClientProperties properties,
CloudSessionMapper sessionMapper) {
this.client = client;
this.properties = properties;
this.sessionMapper = sessionMapper;
}
public Session authenticate(String loginId, String password) {
try {
Duration blockingTimeout = properties.requestTimeout().plusSeconds(1);
return sessionMapper.map(client.authenticate(loginId, password).block(blockingTimeout));
} catch (WebClientResponseException exception) {
if (exception.getStatusCode() == HttpStatus.UNAUTHORIZED
|| exception.getStatusCode() == HttpStatus.FORBIDDEN) {
throw new CloudAuthenticationException(
CloudAuthenticationException.Reason.INVALID_CREDENTIALS,
"Invalid login credentials",
exception);
}
throw unavailable(exception);
} catch (CloudAuthenticationException exception) {
throw exception;
} catch (RuntimeException exception) {
throw unavailable(exception);
}
}
private CloudAuthenticationException unavailable(RuntimeException cause) {
return new CloudAuthenticationException(
CloudAuthenticationException.Reason.SERVICE_UNAVAILABLE,
"Cloud identity service is unavailable",
cause);
}
}

View File

@@ -0,0 +1,24 @@
package matrix.nimble.cloud.identity;
import com.cygnus.client.CloudClientFactory;
import com.cygnus.client.CloudClientProperties;
import com.cygnus.client.CloudIdentityClient;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.time.Clock;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CloudIdentityConfiguration {
@Bean
CloudClientProperties cloudClientProperties() {
return CloudClientProperties.fromSystem();
}
@Bean
CloudIdentityClient cloudIdentityClient(CloudClientProperties properties) {
ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules();
return CloudClientFactory.create(properties, objectMapper, Clock.systemUTC());
}
}

View File

@@ -0,0 +1,66 @@
package matrix.nimble.cloud.identity;
import com.cygnus.client.model.CloudIdentitySession;
import com.cygnus.client.model.CloudMenuItem;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.List;
import matrix.nimble.model.Session;
import org.springframework.stereotype.Component;
@Component
public class CloudSessionMapper {
private static final DateTimeFormatter LEGACY_LOGIN_TIME =
DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss")
.withZone(ZoneId.systemDefault());
private final SessionMenuRenderer menuRenderer;
public CloudSessionMapper(SessionMenuRenderer menuRenderer) {
this.menuRenderer = menuRenderer;
}
public Session map(CloudIdentitySession source) {
Session target = new Session();
target.setUserID(Short.toString(source.userId()));
target.setUsername(source.loginId());
target.setUserDisplayName(source.displayName());
target.setUserGroupID(Short.toString(source.groupId()));
target.setUserGroupName(source.groupName());
target.setBranchID(Short.toString(source.branchId()));
target.setBranchName(source.branchName());
target.setBranchCode(source.branchCode());
target.setBranchLocation(source.branchLocation());
target.setCompanyID(Short.toString(source.companyId()));
target.setCompanyName(source.companyName());
target.setCompanyCode(source.companyCode());
target.setLoginTime(LEGACY_LOGIN_TIME.format(source.loginTime()));
target.setMenuHtml(menuRenderer.render(toLegacyMenu(source.menu())));
return target;
}
private String[][] toLegacyMenu(List<CloudMenuItem> menu) {
List<CloudMenuItem> items = menu == null
? List.of()
: menu;
String[][] result = new String[items.size()][8];
for (int index = 0; index < items.size(); index++) {
CloudMenuItem item = items.get(index);
result[index] = new String[] {
Short.toString(item.pageId()),
value(item.label()),
value(item.targetUrl()),
Short.toString(item.parentPage()),
Short.toString(item.pageOrder()),
value(item.permission()),
value(item.targetWindow()),
value(item.requestValue())
};
}
return result;
}
private String value(String value) {
return value == null ? "" : value;
}
}

View File

@@ -0,0 +1,52 @@
package matrix.nimble.cloud.identity;
import org.springframework.stereotype.Component;
@Component
public class SessionMenuRenderer {
public String render(String[][] menuContent) {
String menuHtml = "<div style='overflow:visible' id='MenuBar'>"
+ "<form method='post' name='MenuForm' id='MenuForm'>"
+ "<input type='hidden' id='pageval' name='pageval' value='' />"
+ "<ul id='master_0'>"
+ "<li><a rel='nofollow' style='cursor:pointer' "
+ "onclick=\"SubmitForm('logout','_parent','MenuForm')\">Logout</a></li>"
+ "<li><a rel='nofollow' style='cursor:pointer' "
+ "onclick=\"SubmitForm('dashboard','_parent','MenuForm')\">Dashboard</a></li>"
+ "</ul></form></div>";
for (String[] menuItem : menuContent) {
menuHtml = generateMenuItem(
menuHtml,
menuItem[3],
menuItem[2],
menuItem[1],
menuItem[0],
safe(menuItem[6]),
safe(menuItem[7]));
}
return menuHtml.replaceAll("<ul id='master_[^']+'></ul>", "");
}
private String safe(String value) {
return value == null ? "" : value.trim();
}
private String generateMenuItem(
String menuHtml,
String parentId,
String pageUrl,
String menuItemName,
String menuItemId,
String targetWindow,
String requestValue) {
return menuHtml.replace(
"<ul id='master_" + parentId + "'>",
"<ul id='master_" + parentId + "'><li><a rel='nofollow' "
+ "style='cursor:pointer' onclick=\"SubmitMenuCommand('"
+ pageUrl + "','" + targetWindow + "','MenuForm','"
+ requestValue + "')\">" + menuItemName
+ "</a><ul id='master_" + menuItemId + "'></ul></li>");
}
}

View File

@@ -1,15 +1,11 @@
package matrix.nimble.controller;
//application libraries
import java.text.SimpleDateFormat;
import java.util.Map;
import matrix.nimble.model.Authenticate;
import matrix.nimble.model.Login;
import matrix.nimble.model.Session;
import matrix.nimble.utilities.DBFunctions;
import matrix.nimble.utilities.GlobalClass;
import matrix.nimble.utilities.ModuleFunctions;
import java.util.Map;
import matrix.nimble.cloud.identity.CloudAuthenticationException;
import matrix.nimble.cloud.identity.CloudAuthenticationGateway;
import matrix.nimble.model.Login;
import matrix.nimble.model.Session;
//servlet libraries
import jakarta.servlet.http.HttpServletRequest;
@@ -26,62 +22,53 @@ import org.springframework.web.bind.annotation.SessionAttributes;
@Controller
@SessionAttributes("Sessvals")
public class SessionController {
ModuleFunctions modf = new ModuleFunctions("");
public class SessionController {
private final CloudAuthenticationGateway cloudAuthenticationGateway;
public SessionController(CloudAuthenticationGateway cloudAuthenticationGateway) {
this.cloudAuthenticationGateway = cloudAuthenticationGateway;
}
@RequestMapping(value="login",method=RequestMethod.POST )
public String LoginPage(ModelMap model,@RequestHeader Map<String, String> headers)
{
model.addAttribute("login", new Login());
try {
System.out.println(modf.decrypt("jkK2V/bmrPi5b8lrauD4rA=="));
System.out.println(modf.decrypt("prpWWKRu0nxinBMTKorvWA=="));
}catch(Exception exce) {
}
String host = headers.get("host").toString();
public String LoginPage(ModelMap model,@RequestHeader Map<String, String> headers)
{
model.addAttribute("login", new Login());
String host = headers.get("host").toString();
if(!host.contains("192.168.10.205:8585") &&
!host.contains("192.168.10.250:8585") &&
!host.startsWith("localhost:") && !host.startsWith("127.0.0.1:")) {
return "error";
}
System.out.println(GlobalClass.getStoragePath());
return "login";
}
return "login";
}
@RequestMapping(value="logout",method=RequestMethod.POST )
public String LogoutPage(HttpServletRequest request, ModelMap model,@ModelAttribute(value="Sessvals") Session Sessvals)
{
DBFunctions DBF=new DBFunctions("1000");
DBF.setProcessFlag(true);
HttpSession session = request.getSession(false);
if (session != null) {
session.invalidate();
}
DBF.FetchRunQuery(15, (new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(new java.util.Date())+"#S#"+request.getRemoteAddr()+"#S#"+Sessvals.getLoginTime()+"#S#"+Sessvals.getUsername()+"#S#").split("#S#"));
model.addAttribute("login", new Login());
return "login";
}
@RequestMapping(value="authenticatelogin",method=RequestMethod.POST )
public String Authenticate(ModelMap model,HttpServletRequest request,@ModelAttribute(value="login") Login login)
{
Authenticate authenticate=new Authenticate();
authenticate.setErrCode("1000");
Session Sessvals=authenticate.AuthenticateLogin(login.getLoginid(), login.getPassword());
String ErrMsg=authenticate.getErrMsg();
if(!authenticate.isProcessFlag())
{
login.setErrMsg(ErrMsg);
login.setPassword("");
model.addAttribute("login",login);
return "login";
}
else
{
model.remove("login");
model.addAttribute("Sessvals",Sessvals);
return "home";
}
}
public String LogoutPage(HttpServletRequest request, ModelMap model,@ModelAttribute(value="Sessvals") Session Sessvals)
{
HttpSession session = request.getSession(false);
if (session != null) {
session.invalidate();
}
model.addAttribute("login", new Login());
return "login";
}
@RequestMapping(value="authenticatelogin",method=RequestMethod.POST )
public String Authenticate(ModelMap model,@ModelAttribute(value="login") Login login)
{
try {
Session cloudSession = cloudAuthenticationGateway.authenticate(
login.getLoginid(), login.getPassword());
model.remove("login");
model.addAttribute("Sessvals", cloudSession);
return "home";
} catch (CloudAuthenticationException exception) {
login.setErrMsg(exception.reason()
== CloudAuthenticationException.Reason.INVALID_CREDENTIALS
? "Invalid login ID or password."
: "Authentication service is temporarily unavailable.");
login.setPassword("");
model.addAttribute("login", login);
return "login";
}
}
@RequestMapping(value="dashboard",method=RequestMethod.POST )
public String Dashboard(ModelMap model,HttpServletRequest request,@ModelAttribute(value="Sessvals") Session Sessvals)
{

View File

@@ -1,105 +0,0 @@
package matrix.nimble.model;
import matrix.nimble.utilities.DBFunctions;
import matrix.nimble.utilities.StringFunctions;
import matrix.nimble.utilities.GlobalClass;
public class Authenticate {
Session SessVals;
String ErrMsg;
String ErrCode;
boolean ProcessFlag;
DBFunctions DBFunc;
private String getErrCode() {
return ErrCode;
}
public void setErrCode(String errCode) {
ErrCode = errCode;
}
public boolean isProcessFlag() {
return ProcessFlag;
}
public void setProcessFlag(boolean processFlag) {
ProcessFlag = processFlag;
}
public String getErrMsg() {
return ErrMsg;
}
private void setErrMsg(String errMsg) {
ErrMsg = errMsg;
}
public Session AuthenticateLogin(String Loginid,String Password)
{
SessVals=new Session();
DBFunc= new DBFunctions(getErrCode());
String RowData=DBFunc.ValidateLogin(Loginid, Password);
if(DBFunc.isProcessFlag())
{
setProcessFlag(true);
setErrMsg(RowData);
SessVals.setUsername(Loginid);
SessVals.setLoginTime(RowData.split(GlobalClass.ColDelim)[0]);
SessVals.setUserID(RowData.split(GlobalClass.ColDelim)[1]);
SessVals.setUserGroupID(RowData.split(GlobalClass.ColDelim)[2]);
SessVals.setUserGroupName(RowData.split(GlobalClass.ColDelim)[3]);
SessVals.setBranchID(RowData.split(GlobalClass.ColDelim)[4]);
SessVals.setBranchName(RowData.split(GlobalClass.ColDelim)[5]);
SessVals.setBranchCode(RowData.split(GlobalClass.ColDelim)[6]);
SessVals.setBranchLocation(RowData.split(GlobalClass.ColDelim)[7]);
SessVals.setCompanyID(RowData.split(GlobalClass.ColDelim)[8]);
SessVals.setCompanyName(RowData.split(GlobalClass.ColDelim)[9]);
SessVals.setCompanyCode(RowData.split(GlobalClass.ColDelim)[10]);
SessVals.setUserDisplayName(RowData.split(GlobalClass.ColDelim)[11]);
RowData=DBFunc.GatherMenuContent(Integer.parseInt(SessVals.getUserGroupID()),Integer.parseInt(SessVals.getUserID()));
if(!DBFunc.isProcessFlag())
{
DBFunc.CreateLogEntry(SessVals.getUserID(), DBFunc.getErrMsg(),DBFunc.getErrDesc(),SessVals.getBranchID());
setProcessFlag(false);
setErrMsg(DBFunc.getErrMsg());
}
else
{
StringFunctions StrFunc=new StringFunctions(getErrCode());
String MenuItems[][]=StrFunc.ConverTo2DArray(RowData, GlobalClass.RowDelim, GlobalClass.ColDelim);
if(StrFunc.isProcessFlag())
{
SessVals.setMenuHtml(CreateUserMenu(MenuItems));
}
else
{
setProcessFlag(false);
setErrMsg(StrFunc.getErrMsg());
}
}
}
else
{
DBFunc.setProcessFlag(true);
DBFunc.CreateLogEntry("0", DBFunc.getErrMsg(),DBFunc.getErrDesc(),"0");
setProcessFlag(false);
setErrMsg(RowData);
}
return SessVals;
}
String CreateUserMenu(String [][] menuContent)
{
String MenuHTML="<div style='overflow:visible' id='MenuBar'><form method='post' name='MenuForm' id='MenuForm'><input type='hidden' id='pageval' name='pageval' value='' /><ul id='master_0'><li><a rel='nofollow' style='cursor:pointer' onclick=\"SubmitForm('logout','_parent','MenuForm')\">Logout</a></li><li><a rel='nofollow' style='cursor:pointer' onclick=\"SubmitForm('dashboard','_parent','MenuForm')\">Dashboard</a></li></ul></form></div>";
for(int RowIndx=0;RowIndx<menuContent.length;RowIndx++)
{
MenuHTML=GenerateMenuItem(MenuHTML, menuContent[RowIndx][3], menuContent[RowIndx][2], menuContent[RowIndx][1], menuContent[RowIndx][0],menuContent[RowIndx][6].trim(),menuContent[RowIndx][7].trim());
}
return MenuHTML.replaceAll("<ul id='master_[^']+'></ul>", "");
}
private String GenerateMenuItem(String MenuHTML,String ParentID,String PageURL,String MenuItemName,String MenuItemID,String TargetWindow,String RequestVal)
{
MenuHTML=MenuHTML.replace("<ul id='master_"+ParentID+"'>","<ul id='master_"+ParentID+"'><li><a rel='nofollow' style='cursor:pointer' onclick=\"SubmitMenuCommand('"+PageURL+"','"+TargetWindow+"','MenuForm','"+RequestVal+"')\">"+MenuItemName+"</a><ul id='master_"+MenuItemID+"'></ul></li>");
return MenuHTML;
}
}

View File

@@ -1,11 +1,9 @@
package matrix.nimble.utilities;
import java.sql.*;
import java.text.SimpleDateFormat;
import java.util.Hashtable;
import java.sql.*;
import java.util.Hashtable;
public class DBFunctions {
private Connection con;
private ModuleFunctions ModFunc;
private String ErrMsg;
private String ErrCode;
private String ErrDesc;
@@ -29,7 +27,6 @@ public class DBFunctions {
private void InitConfiguration()
{
FFunc = new FileFunctions(getErrCode());
ModFunc = new ModuleFunctions(getErrCode());
try {
FFunc.CollectDBConf(); //Initialize Database connection parameters from nimbledb.wcnf file
setQueryMode(FFunc.getQueryMode());
@@ -395,77 +392,6 @@ public class DBFunctions {
System.out.println(Query);
return Query;
}
public String ValidateLogin(String Loginid, String Password)
{
String RowData="";
setProcessFlag(true);
try
{
Password=new ModuleFunctions(getErrCode()).encrypt(Password);
RowData=FetchRunQuery(1,(Loginid+"#S#"+Password).split("#S#"));
if(!isProcessFlag())
{
if(getErrMsg().contains("No records found."))
{
setErrMsg(getErrMsg().replace("No records found.", "Invalid username or password."));
}
return getErrMsg();
}
else
{
if(RowData.split(GlobalClass.ColDelim)[11].equals("0"))
{
setErrMsg(getErrCode()+"USBLK:Info:User is blocked. Please contact your administrator");
setErrDesc("Login for user "+Loginid+" has been blocked by administrator.");
setProcessFlag(false);
return getErrMsg();
}
else
{
String LoginTime=new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(new java.util.Date());
FetchRunQuery(14, (Loginid+"#S#"+LoginTime+"#S#").split("#S#"));
setProcessFlag(true);
setErrMsg("0000:Info:Login successfull");
return LoginTime+GlobalClass.ColDelim+RowData;
}
}
}catch(Exception exce)
{
System.out.println(exce.getMessage()+"---------");
exce.printStackTrace();
setErrMsg(getErrCode()+"ENCPW:Error: Connection with server has been lost. Please check your network connection.");
setErrDesc(exce.getMessage());
setProcessFlag(false);
return getErrMsg();
}
}
public String GatherMenuContent(int UserGroupID,int UserId)
{
String MenuContent="";
try
{
MenuContent=FetchRunQuery(2,(UserGroupID+","+UserId).split(","));
if(!isProcessFlag())
{
if(getErrMsg().contains("No records found."))
{
setErrMsg(getErrMsg().replace("No records found.", "Access denied for user. Please contact your system administrator"));
setErrDesc("Access denied.");
setProcessFlag(false);
}
}
}catch(Exception exce)
{
setErrMsg(getErrCode()+"MNCNT:Error: Menu creation failed. Please report this issue to our software support team.");
setErrDesc(exce.getMessage());
setProcessFlag(false);
}
finally
{
CloseConnection();
}
return MenuContent;
}
private String fetchData(ResultSet RstSet,String RowDelimeter,String ColDelimeter) throws SQLException
{
ResultSetMetaData rsmd;

File diff suppressed because one or more lines are too long

View File

@@ -12,9 +12,10 @@ class FileFunctionsQueryCacheTest {
void loadsQueriesFromNimbleQueryFileAndHandlesMissingCodes() throws Exception {
FileFunctions files = new FileFunctions("TEST");
String query = files.GetQuery(1);
String query = files.GetQuery(3);
assertTrue(query.startsWith("select!C0L!select u.user_id"));
assertTrue(query.startsWith("select!C0L!select portfolio_id"));
assertNull(files.GetQuery(1));
assertNull(files.GetQuery(Integer.MAX_VALUE));
}
}

View File

@@ -0,0 +1,52 @@
package matrix.nimble.cloud.identity;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.cygnus.client.model.CloudIdentitySession;
import com.cygnus.client.model.CloudMenuItem;
import java.time.Instant;
import java.util.List;
import matrix.nimble.model.Session;
import org.junit.jupiter.api.Test;
class CloudSessionMapperTest {
@Test
void mapsCloudIdentityAndStructuredMenuToLegacySession() {
CloudIdentitySession identity = new CloudIdentitySession(
(short) 17,
"maddy",
"Maddy",
(short) 5,
"Operations",
(short) 9,
"Mayapuri",
"MYP",
"Delhi",
(short) 2,
"Matrix",
"MCR",
Instant.parse("2026-07-23T06:30:00Z"),
List.of(
new CloudMenuItem(
(short) 10, "Operations", "", (short) 0,
(short) 1, "111", null, null),
new CloudMenuItem(
(short) 11, "Scanning", "scanreporting", (short) 10,
(short) 1, "111", "_parent", "")));
Session session = new CloudSessionMapper(new SessionMenuRenderer()).map(identity);
assertEquals("17", session.getUserID());
assertEquals("maddy", session.getUsername());
assertEquals("Operations", session.getUserGroupName());
assertEquals("Mayapuri", session.getBranchName());
assertEquals("Matrix", session.getCompanyName());
assertFalse(session.getLoginTime().isBlank());
assertTrue(session.getMenuHtml().contains("Operations"));
assertTrue(session.getMenuHtml().contains("Scanning"));
assertTrue(session.getMenuHtml().contains("scanreporting"));
}
}

View File

@@ -1,11 +1,12 @@
package matrix.nimble.model;
package matrix.nimble.cloud.identity;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import matrix.nimble.model.Session;
import org.junit.jupiter.api.Test;
class AuthenticateMenuTest {
class SessionMenuRendererTest {
@Test
void generatesSessionMenuWithoutEmptyLeafSubmenus() {
@@ -14,7 +15,7 @@ class AuthenticateMenuTest {
{ "11", "Telecalling", "telecalling", "10", "", "", "_parent", "mode" }
};
String menuHtml = new Authenticate().CreateUserMenu(menuItems);
String menuHtml = new SessionMenuRenderer().render(menuItems);
assertTrue(menuHtml.contains(">Operations<"));
assertTrue(menuHtml.contains(">Telecalling<"));

Binary file not shown.

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More