diff --git a/.gcp.env b/.gcp.env index feea70e7..18acbce6 100644 --- a/.gcp.env +++ b/.gcp.env @@ -1,4 +1,4 @@ -SENTRIUS_VERSION=1.0.44 +SENTRIUS_VERSION=1.0.45 SENTRIUS_SSH_VERSION=1.0.4 SENTRIUS_KEYCLOAK_VERSION=1.0.7 SENTRIUS_AGENT_VERSION=1.0.18 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 99d81b4d..6c9c6eca 100644 --- a/.gitignore +++ b/.gitignore @@ -33,9 +33,9 @@ replay_pid* target/* api/target/** core/target/** -analyagents/target/** +java-agent/target/** core/target/ -analyagents/target/ +java-agent/target/ node/* node_modules/* api/node_modules/* diff --git a/api/dynamic.properties b/api/dynamic.properties index 85368675..1a27b433 100644 --- a/api/dynamic.properties +++ b/api/dynamic.properties @@ -7,6 +7,7 @@ sshEnabled=true systemLogoName=Sentrius AccessTokenAuditor.rule.4=io.sentrius.sso.automation.auditing.rules.OpenAISessionRule;Malicious AI Monitoring AccessTokenAuditor.rule.5=io.sentrius.sso.automation.auditing.rules.TwoPartyAIMonitor;AI Second Party Monitor +AccessTokenAuditor.rule.6=io.sentrius.sso.automation.auditing.rules.SudoApproval;Sudo Approval allowProxies=true AccessTokenAuditor.rule.2=io.sentrius.sso.automation.auditing.rules.DeletePrevention;Delete Prevention AccessTokenAuditor.rule.3=io.sentrius.sso.automation.auditing.rules.TwoPartySessionRule;Require Second Party Monitoring diff --git a/api/src/main/java/io/sentrius/sso/controllers/api/ChatApiController.java b/api/src/main/java/io/sentrius/sso/controllers/api/ChatApiController.java new file mode 100644 index 00000000..4e5f849a --- /dev/null +++ b/api/src/main/java/io/sentrius/sso/controllers/api/ChatApiController.java @@ -0,0 +1,93 @@ +package io.sentrius.sso.controllers.api; + +import java.security.GeneralSecurityException; +import java.time.ZoneOffset; +import java.util.List; +import java.util.stream.Collectors; +import io.sentrius.sso.core.utils.AccessUtil; +import io.sentrius.sso.protobuf.Session.ChatMessage; +import io.sentrius.sso.core.config.SystemOptions; +import io.sentrius.sso.core.controllers.BaseController; +import io.sentrius.sso.core.model.security.enums.SSHAccessEnum; +import io.sentrius.sso.core.model.sessions.SessionLog; +import io.sentrius.sso.core.repository.ChatLogRepository; +import io.sentrius.sso.core.security.service.CryptoService; +import io.sentrius.sso.core.services.ErrorOutputService; +import io.sentrius.sso.core.services.UserService; +import io.sentrius.sso.core.services.auditing.AuditService; +import io.sentrius.sso.core.services.terminal.SessionTrackingService; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +@RequestMapping("/api/v1/chat") +public class ChatApiController extends BaseController { + private final AuditService auditService; + final CryptoService cryptoService; + final SessionTrackingService sessionTrackingService; + final ChatLogRepository chatLogRepository; + + public ChatApiController( + UserService userService, + SystemOptions systemOptions, + ErrorOutputService errorOutputService, + AuditService auditService, + CryptoService cryptoService, SessionTrackingService sessionTrackingService, ChatLogRepository chatLogRepository + ) { + super(userService, systemOptions, errorOutputService); + this.auditService = auditService; + this.cryptoService = cryptoService; + this.sessionTrackingService = sessionTrackingService; + this.chatLogRepository = chatLogRepository; + } + + public SessionLog createSession(@RequestParam String username, @RequestParam String ipAddress) { + return auditService.createSession(username, ipAddress); + } + + @GetMapping("/history") + public ResponseEntity> getChatHistory( + HttpServletRequest request, + HttpServletResponse response, + @RequestParam(name="sessionId") String sessionIdEncrypted, + @RequestParam(name="chatGroupId") String chatGroupIdEncrypted) + throws GeneralSecurityException { + + Long sessionId = Long.parseLong(cryptoService.decrypt(sessionIdEncrypted)); + + // Check if the user has access to this session + var myConnectedSystem = sessionTrackingService.getConnectedSession(sessionId); + + var user = getOperatingUser(request, response); + + if (myConnectedSystem == null || + ( + !myConnectedSystem.getUser().getId().equals(user.getId()) && + !AccessUtil.canAccess(user, SSHAccessEnum.CAN_MANAGE_SYSTEMS))) { + return ResponseEntity.status(403).body(null); // Forbidden access + } + + + String chatGroupId = cryptoService.decrypt(chatGroupIdEncrypted); + List messages = chatLogRepository.findBySessionIdAndChatGroupId(sessionId, chatGroupId) + .stream() + .map(chatLog -> ChatMessage.newBuilder() + .setSessionId(sessionId) + .setChatGroupId(chatGroupId) + .setSender(chatLog.getSender()) + .setMessage(chatLog.getMessage()) + .setTimestamp(chatLog.getMessageTimestamp().toEpochSecond(ZoneOffset.UTC)).build()) + .collect(Collectors.toList()); + + return ResponseEntity.ok(messages); + } + + +} diff --git a/api/src/main/java/io/sentrius/sso/controllers/view/HostController.java b/api/src/main/java/io/sentrius/sso/controllers/view/HostController.java index 3099881d..ff61f931 100644 --- a/api/src/main/java/io/sentrius/sso/controllers/view/HostController.java +++ b/api/src/main/java/io/sentrius/sso/controllers/view/HostController.java @@ -202,7 +202,7 @@ public String connectSSHServer( model.addAttribute("enclaveConfiguration", config); - return "sso/ssh/secure_shell"; + return "sso/ssh/sso"; } @@ -243,7 +243,7 @@ public String attachSession( model.addAttribute("enclaveConfiguration", config); - return "sso/ssh/secure_shell"; + return "sso/ssh/sso"; } diff --git a/api/src/main/java/io/sentrius/sso/controllers/view/ZeroTrustATController.java b/api/src/main/java/io/sentrius/sso/controllers/view/ZeroTrustATController.java index fec8c593..a4f5c856 100644 --- a/api/src/main/java/io/sentrius/sso/controllers/view/ZeroTrustATController.java +++ b/api/src/main/java/io/sentrius/sso/controllers/view/ZeroTrustATController.java @@ -1,13 +1,17 @@ package io.sentrius.sso.controllers.view; +import java.util.List; import io.sentrius.sso.core.annotations.LimitAccess; import io.sentrius.sso.core.config.SystemOptions; import io.sentrius.sso.core.controllers.BaseController; +import io.sentrius.sso.core.model.dto.JITTrackerDTO; import io.sentrius.sso.core.model.security.enums.ZeroTrustAccessTokenEnum; import io.sentrius.sso.core.model.users.User; import io.sentrius.sso.core.services.ErrorOutputService; import io.sentrius.sso.core.services.ZeroTrustRequestService; import io.sentrius.sso.core.services.UserService; +import io.sentrius.sso.core.utils.AccessUtil; +import io.sentrius.sso.core.utils.ZTATUtils; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.http.ResponseEntity; @@ -53,13 +57,38 @@ public String viewMyTats(HttpServletRequest request, HttpServletResponse respons return "sso/ztats/view_my_ztats"; } + + List decorateTats(List tats, User operatingUser){ + boolean canApprove = AccessUtil.canAccess(operatingUser, ZeroTrustAccessTokenEnum.CAN_APPROVE_ZTATS); + boolean canDeny = AccessUtil.canAccess(operatingUser, ZeroTrustAccessTokenEnum.CAN_DENY_ZTATS); + if (canApprove || canDeny) { + for (var tat : tats) { + + if (tat.getUserName().equals(operatingUser.getUsername())) { + tat.setCurrentUser(true); + if (systemOptions.getCanApproveOwnZtat()) { + tat.setCanApprove(canApprove); + tat.setCanDeny(canDeny); + } + } + else { + tat.setCanApprove(canApprove); + tat.setCanDeny(canDeny); + } + + } + } + return tats; + } + private void modelTATs(Model model, User operatingUser){ - model.addAttribute("openTerminalTats", ztatRequestService.getOpenAccessTokenRequests(operatingUser)); - model.addAttribute("openOpsTats", ztatRequestService.getOpenOpsRequests(operatingUser)); - model.addAttribute("approvedTerminalTats", ztatRequestService.getApprovedTerminalAccessTokenRequests(operatingUser)); - model.addAttribute("approvedOpsTats", ztatRequestService.getApprovedOpsAccessTokenRequests(operatingUser)); - model.addAttribute("deniedOpsTats", ztatRequestService.getDeniedOpsAccessTokenRequests(operatingUser)); - model.addAttribute("deniedTerminalTats", ztatRequestService.getDeniedTerminalAccessTokenRequests(operatingUser)); + model.addAttribute("openTerminalTats", + decorateTats(ztatRequestService.getOpenAccessTokenRequests(operatingUser),operatingUser)); + model.addAttribute("openOpsTats", decorateTats(ztatRequestService.getOpenOpsRequests(operatingUser),operatingUser)); + model.addAttribute("approvedTerminalTats", decorateTats(ztatRequestService.getApprovedTerminalAccessTokenRequests(operatingUser),operatingUser)); + model.addAttribute("approvedOpsTats", decorateTats(ztatRequestService.getApprovedOpsAccessTokenRequests(operatingUser),operatingUser)); + model.addAttribute("deniedOpsTats",decorateTats( ztatRequestService.getDeniedOpsAccessTokenRequests(operatingUser),operatingUser)); + model.addAttribute("deniedTerminalTats", decorateTats(ztatRequestService.getDeniedTerminalAccessTokenRequests(operatingUser),operatingUser)); } } diff --git a/api/src/main/java/io/sentrius/sso/websocket/ChatListenerService.java b/api/src/main/java/io/sentrius/sso/websocket/ChatListenerService.java new file mode 100644 index 00000000..5aa21ba3 --- /dev/null +++ b/api/src/main/java/io/sentrius/sso/websocket/ChatListenerService.java @@ -0,0 +1,354 @@ +package io.sentrius.sso.websocket; + +import java.security.GeneralSecurityException; +import java.time.LocalDateTime; +import java.util.Base64; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import com.fasterxml.jackson.core.JsonProcessingException; +import io.sentrius.sso.core.model.ConnectedSystem; +import io.sentrius.sso.core.model.chat.ChatLog; +import io.sentrius.sso.core.security.service.CryptoService; +import io.sentrius.sso.core.services.ChatService; +import io.sentrius.sso.core.services.IntegrationSecurityTokenService; +import io.sentrius.sso.core.services.terminal.SessionTrackingService; +import io.sentrius.sso.core.utils.JsonUtil; +import io.sentrius.sso.genai.ChatConversation; +import io.sentrius.sso.genai.GenerativeAPI; +import io.sentrius.sso.genai.GeneratorConfiguration; +import io.sentrius.sso.genai.model.ChatResponse; +import io.sentrius.sso.genai.model.Conversation; +import io.sentrius.sso.integrations.external.ExternalIntegrationDTO; +import io.sentrius.sso.protobuf.Session; +import io.sentrius.sso.security.ApiKey; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ChatListenerService { + + private final SessionTrackingService sessionTrackingService; + private final CryptoService cryptoService; + + @Qualifier("taskExecutor") // Specify the custom task executor to use + private final Executor taskExecutor; + private final IntegrationSecurityTokenService integrationSecurityTokenService; + + private final SshListenerService sshListenerService; + private final ChatService chatService; + + private final ConcurrentMap activeSessions = new ConcurrentHashMap<>(); + + + public void startAuditingSession(String terminalSessionId, WebSocketSession session) throws GeneralSecurityException { + + var sessionIdStr = cryptoService.decrypt(terminalSessionId); + var sessionIdLong = Long.parseLong(sessionIdStr); + var connectedSystem = sessionTrackingService.getConnectedSession(sessionIdLong); + if (null != connectedSystem ) { + connectedSystem.setWebsocketListenerSessionId(session.getId()); + } + } + + public void endAuditingSession(String terminalSessionId) throws GeneralSecurityException { + + var sessionIdStr = cryptoService.decrypt(terminalSessionId); + var sessionIdLong = Long.parseLong(sessionIdStr); + var connectedSystem = sessionTrackingService.getConnectedSession(sessionIdLong); + if (null != connectedSystem ) { + connectedSystem.setWebsocketListenerSessionId(""); + } + } + + public void startChatListener(String terminalSessionId, WebSocketSession session) throws GeneralSecurityException { + + var sessionIdStr = cryptoService.decrypt(terminalSessionId); + var sessionIdLong = Long.parseLong(sessionIdStr); + + + var connectedSystem = sessionTrackingService.getConnectedSession(sessionIdLong); + + log.info("Starting to listen to SSH server for session: {}", terminalSessionId); + + activeSessions.putIfAbsent(terminalSessionId, session); + + connectedSystem.setWebsocketChatSessionId(session.getId()); + + + + taskExecutor.execute(() -> { + while (!Thread.currentThread().isInterrupted() && activeSessions.get(terminalSessionId) != null && + !connectedSystem.getSession().getClosed()) { + try { + // Mock logic for receiving data from SSH server + var sshData = sessionTrackingService.getOutput(connectedSystem, 1L, TimeUnit.SECONDS, + output -> (!connectedSystem.getSession().getClosed() && (null != activeSessions.get(terminalSessionId) && activeSessions.get(terminalSessionId).isOpen()))); + + // Send data to the specific terminal session + if (null != sshData ) { + + }else { + log.trace("No data to return"); + } + + + + } catch (Exception e) { + log.error("Error while listening to SSH server: ", e); + Thread.currentThread().interrupt(); // Ensure the thread can exit cleanly on exception + } + }; + log.trace("***L:eaving thread"); + }); + } + + + @Async + public void sendToTerminalSession(String terminalSessionId, ConnectedSystem connectedSystem, + Session.ChatMessage sshData) { + } + + private ChatResponse toChatMessage(Session.ChatMessage sshData) { + return ChatResponse.builder().role("user").content(sshData.getMessage()).build(); + } + + private ChatResponse toChatMessage(ChatLog chatLog) { + return ChatResponse.builder().role(chatLog.getSender().equals("agent") ? "system" : "user").content(chatLog.getMessage()).build(); + } + + private List toChatMessages(List chatLogs) { + return chatLogs.stream().map(this::toChatMessage).collect(Collectors.toList()); + } + + public void processMessage( + String sessionId, + WebSocketSession session, ConnectedSystem terminalSessionId, Session.ChatMessage chatMessage) { + var openaiService = integrationSecurityTokenService.findByConnectionType("openai").stream().findFirst().orElse(null); + + if (null != openaiService) { + log.info("OpenAI service is available"); + ExternalIntegrationDTO externalIntegrationDTO = null; + try { + externalIntegrationDTO = JsonUtil.MAPPER.readValue( + openaiService.getConnectionInfo(), + ExternalIntegrationDTO.class + ); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + ApiKey key = + ApiKey.builder().apiKey(externalIntegrationDTO.getApiToken()) + .principal(externalIntegrationDTO.getUsername()).build(); + + var chatConversation = new ChatConversation(key, new GenerativeAPI(key), + GeneratorConfiguration.builder().build()); + + var previousMessages = chatService.findBySessionIdAndChatGroupId(terminalSessionId.getSession().getId()); + Conversation convo = + Conversation.builder().newUserMessage(toChatMessage(chatMessage)).previousMessages(toChatMessages(previousMessages)).build(); + convo.setSystemConfines("You are an agent whose responding to chats form a user on an SSH terminal " + + "session. The history preceding this message will be included. Please respond with a JSON structure " + + "that includes the message you want to send to the user. The message should be in the 'message' field" + + ". If the user requests help directly into the terminal, please return a field labeled 'terminal'. " + + "Since you have the ability to inject commands into the terminal, always ask if they would like you " + + "to place it there before sending the terminal field, but place the question at the end of the " + + "message field, not in terminal, which can be empty. If you are unsure of the user's intent, ask for " + + "clarification. We " + + "will send the " + + "history of your chat with the user. We have another AI agent that asks security related questions. " + + "If these come they will be sent to you as well along with their response. Ensure that the person " + + "answers the question, if they do not or you assess that they are being evasive, send a json field " + + "named alert as a boolean value of true." ); + try { + var chatMessageResponse = chatConversation.generate(convo); + ChatLog userChat = + ChatLog.builder().messageTimestamp(LocalDateTime.now()).chatGroupId("0") + .session(terminalSessionId.getSession()).message(chatMessage.getMessage()).sender( + "user").build(); + chatService.save(userChat); + Session.ChatMessage userMessage = Session.ChatMessage.newBuilder() + .setSender("user") + .setMessage(chatMessage.getMessage()) + .build(); + + byte[] messageBytes = userMessage.toByteArray(); + String base64Message = Base64.getEncoder().encodeToString(messageBytes); + log.trace("Sending message to session: {}", userMessage); + // session.sendMessage(new TextMessage(base64Message)); + if (chatMessageResponse.getTerminalMessage() != null) { + log.info("Sending terminal message to session: {}", chatMessageResponse.getTerminalMessage()); + Session.TerminalMessage terminalMessage = Session.TerminalMessage.newBuilder() + .setType(Session.MessageType.USER_DATA) + .setCommand(chatMessageResponse.getTerminalMessage()) + .build(); + sshListenerService.sendToTerminalSession(sessionId, terminalSessionId, + terminalMessage); + sshListenerService.processTerminalMessage(terminalSessionId, terminalMessage); + if (chatMessageResponse.getContent().isEmpty()) { + Session.ChatMessage chatMessage1 = Session.ChatMessage.newBuilder() + .setSender("agent") + .setMessage("I've added the command to your terminal!") + .build(); + messageBytes = chatMessage1.toByteArray(); + base64Message = Base64.getEncoder().encodeToString(messageBytes); + log.trace("Sending message to session: {}", chatMessage1); + session.sendMessage(new TextMessage(base64Message)); + } + } + if (chatMessageResponse.isAlert()){ + log.info("**** alerttttttt"); + } + if (!chatMessageResponse.getContent().isEmpty()) { + Session.ChatMessage chatMessage1 = Session.ChatMessage.newBuilder() + .setSender("agent") + .setMessage(chatMessageResponse.getContent()) + .build(); + + + + + + ChatLog chatLog = + ChatLog.builder().messageTimestamp(LocalDateTime.now()).chatGroupId("0").session(terminalSessionId.getSession()).message(chatMessage1.getMessage()).sender( + "agent").build(); + chatService.save(chatLog); + + messageBytes = chatMessage1.toByteArray(); + base64Message = Base64.getEncoder().encodeToString(messageBytes); + log.trace("Sending message to session: {}", chatMessage1); + session.sendMessage(new TextMessage(base64Message)); + } + + + + + + + + + + } catch (Exception e) { + e.printStackTrace(); + log.error("Error categorizing command", e); + } + + } else { + log.info("OpenAI service is not available"); + } + } + + + public void stopListeningToSshServer(ConnectedSystem connectedSystem) { + sessionTrackingService.closeSession(connectedSystem); + } + + /** Maps key press events to the ascii values */ + static Map keyMap = new HashMap<>(); + + static { + // ESC + keyMap.put(27, new byte[] {(byte) 0x1b}); + // ENTER + keyMap.put(13, new byte[] {(byte) 0x0d}); + // LEFT + keyMap.put(37, new byte[] {(byte) 0x1b, (byte) 0x4f, (byte) 0x44}); + // UP + keyMap.put(38, new byte[] {(byte) 0x1b, (byte) 0x4f, (byte) 0x41}); + // RIGHT + keyMap.put(39, new byte[] {(byte) 0x1b, (byte) 0x4f, (byte) 0x43}); + // DOWN + keyMap.put(40, new byte[] {(byte) 0x1b, (byte) 0x4f, (byte) 0x42}); + // BS + keyMap.put(8, new byte[] {(byte) 0x7f}); + // TAB + keyMap.put(9, new byte[] {(byte) 0x09}); + // CTR + keyMap.put(17, new byte[] {}); + // DEL + keyMap.put(46, "\033[3~".getBytes()); + // CTR-A + keyMap.put(65, new byte[] {(byte) 0x01}); + // CTR-B + keyMap.put(66, new byte[] {(byte) 0x02}); + // CTR-C + keyMap.put(67, new byte[] {(byte) 0x03}); + // CTR-D + keyMap.put(68, new byte[] {(byte) 0x04}); + // CTR-E + keyMap.put(69, new byte[] {(byte) 0x05}); + // CTR-F + keyMap.put(70, new byte[] {(byte) 0x06}); + // CTR-G + keyMap.put(71, new byte[] {(byte) 0x07}); + // CTR-H + keyMap.put(72, new byte[] {(byte) 0x08}); + // CTR-I + keyMap.put(73, new byte[] {(byte) 0x09}); + // CTR-J + keyMap.put(74, new byte[] {(byte) 0x0A}); + // CTR-K + keyMap.put(75, new byte[] {(byte) 0x0B}); + // CTR-L + keyMap.put(76, new byte[] {(byte) 0x0C}); + // CTR-M + keyMap.put(77, new byte[] {(byte) 0x0D}); + // CTR-N + keyMap.put(78, new byte[] {(byte) 0x0E}); + // CTR-O + keyMap.put(79, new byte[] {(byte) 0x0F}); + // CTR-P + keyMap.put(80, new byte[] {(byte) 0x10}); + // CTR-Q + keyMap.put(81, new byte[] {(byte) 0x11}); + // CTR-R + keyMap.put(82, new byte[] {(byte) 0x12}); + // CTR-S + keyMap.put(83, new byte[] {(byte) 0x13}); + // CTR-T + keyMap.put(84, new byte[] {(byte) 0x14}); + // CTR-U + keyMap.put(85, new byte[] {(byte) 0x15}); + // CTR-V + keyMap.put(86, new byte[] {(byte) 0x16}); + // CTR-W + keyMap.put(87, new byte[] {(byte) 0x17}); + // CTR-X + keyMap.put(88, new byte[] {(byte) 0x18}); + // CTR-Y + keyMap.put(89, new byte[] {(byte) 0x19}); + // CTR-Z + keyMap.put(90, new byte[] {(byte) 0x1A}); + // CTR-[ + keyMap.put(219, new byte[] {(byte) 0x1B}); + // CTR-] + keyMap.put(221, new byte[] {(byte) 0x1D}); + // INSERT + keyMap.put(45, "\033[2~".getBytes()); + // PG UP + keyMap.put(33, "\033[5~".getBytes()); + // PG DOWN + keyMap.put(34, "\033[6~".getBytes()); + // END + keyMap.put(35, "\033[4~".getBytes()); + // HOME + keyMap.put(36, "\033[1~".getBytes()); + } + + public void removeSession(String sessionId) { + log.trace("Removing session: {}", sessionId); + activeSessions.remove(sessionId); + } +} diff --git a/api/src/main/java/io/sentrius/sso/websocket/ChatWSHandler.java b/api/src/main/java/io/sentrius/sso/websocket/ChatWSHandler.java new file mode 100644 index 00000000..074e73e8 --- /dev/null +++ b/api/src/main/java/io/sentrius/sso/websocket/ChatWSHandler.java @@ -0,0 +1,159 @@ + +package io.sentrius.sso.websocket; + +import java.io.IOException; +import java.net.URI; +import java.security.GeneralSecurityException; +import java.util.Base64; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import io.sentrius.sso.core.security.service.CryptoService; +import io.sentrius.sso.core.services.metadata.TerminalSessionMetadataService; +import io.sentrius.sso.core.services.terminal.SessionTrackingService; +import io.sentrius.sso.protobuf.Session; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +@Slf4j +@Component +@RequiredArgsConstructor +public class ChatWSHandler extends TextWebSocketHandler { + + + final SessionTrackingService sessionTrackingService; + final ChatListenerService chatListenerService; + final CryptoService cryptoService; + final TerminalSessionMetadataService terminalSessionMetadataService; + + + + // Store active sessions, using session ID or a custom identifier + private final ConcurrentHashMap sessions = new ConcurrentHashMap<>(); + + @Override + public void afterConnectionEstablished(WebSocketSession session) throws Exception { + // Extract query parameters from the URI + URI uri = session.getUri(); + if (uri != null) { + Map queryParams = parseQueryParams(uri.getQuery()); + String sessionId = queryParams.get("sessionId"); + + + + if (sessionId != null) { + // Store the WebSocket session using the session ID from the query parameter + sessions.put(sessionId, session); + log.info("New connection established, session ID: " + sessionId); + // until we have another human on the other side we don't need a thread for this. + //chatListenerService.startChatListener(sessionId, session); + + } else { + log.info("Session ID not found in query parameters."); + session.close(); // Close the session if no valid session ID is provided + } + } else { + log.info("No URI available for this session."); + session.close(); // Close the session if URI is unavailable + } + } + + @Override + protected void handleTextMessage(WebSocketSession session, TextMessage message) + throws IOException, GeneralSecurityException { + + // Extract query parameters from the URI again if needed + URI uri = session.getUri(); + log.info("got message {}", uri); + try { + if (uri != null) { + Map queryParams = parseQueryParams(uri.getQuery()); + String sessionId = queryParams.get("sessionId"); + + if (sessionId != null) { + log.info("Received message from session ID: " + sessionId); + // Handle the message (e.g., process or respond) + + + // Deserialize the protobuf message + byte[] messageBytes = Base64.getDecoder().decode(message.getPayload()); + Session.ChatMessage auditLog = + Session.ChatMessage.parseFrom(messageBytes); + if (auditLog.getMessage().equals("heartbeat")){ + log.info("heartbeat"); + return; + } + // Decrypt the session ID +// var sessionIdStr = cryptoService.decrypt(sessionId); + // var sessionIdLong = Long.parseLong(sessionIdStr); + var lookupId = sessionId + "=="; + // Retrieve ConnectedSystem from your persistent map using the session ID + var sys = sessionTrackingService.getEncryptedConnectedSession(lookupId); + if (null != sys ) { + log.info("oh"); + // Get the user's session and handle trigger if present + chatListenerService.processMessage(sessionId, session, sys, auditLog); + } + } else { + log.info("Session ID not found in query parameters for message handling."); + } + } + }catch (Exception e ){ + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + @Override + public void afterConnectionClosed(WebSocketSession session, org.springframework.web.socket.CloseStatus status) throws Exception { + URI uri = session.getUri(); + if (uri != null) { + Map queryParams = parseQueryParams(uri.getQuery()); + String sessionId = queryParams.get("sessionId"); + + if (sessionId != null) { + // Remove the session when connection is closed + var lookupId = sessionId + "=="; + + + sessions.remove(sessionId); + chatListenerService.removeSession(sessionId); + + log.info("Connection closed, session ID: " + sessionId); + } + } + } + + // Utility method to parse query parameters + private Map parseQueryParams(String query) { + if (query == null || query.isEmpty()) { + return Map.of(); + } + return Stream.of(query.split("&")) + .map(param -> param.split("=")) + .collect(Collectors.toMap( + param -> param[0], + param -> param.length > 1 ? param[1] : "" + )); + } + + // Utility method to send a message to a specific session + public void sendMessageToSession(String sessionId, String message) { + WebSocketSession session = sessions.get(sessionId); + if (session != null && session.isOpen()) { + try { + session.sendMessage(new TextMessage(message)); + } catch (IOException e) { + System.err.println("Error sending message to session " + sessionId); + e.printStackTrace(); + } + } else { + System.err.println("Session not found or already closed: " + sessionId); + } + } +} diff --git a/api/src/main/java/io/sentrius/sso/websocket/SshListenerService.java b/api/src/main/java/io/sentrius/sso/websocket/SshListenerService.java index da860bd6..29a81d5a 100644 --- a/api/src/main/java/io/sentrius/sso/websocket/SshListenerService.java +++ b/api/src/main/java/io/sentrius/sso/websocket/SshListenerService.java @@ -76,7 +76,7 @@ public void startListeningToSshServer(String terminalSessionId, WebSocketSession while (!Thread.currentThread().isInterrupted() && activeSessions.get(terminalSessionId) != null && !connectedSystem.getSession().getClosed()) { try { - // Mock logic for receiving data from SSH server + // logic for receiving data from SSH server var sshData = sessionTrackingService.getOutput(connectedSystem, 1L, TimeUnit.SECONDS, output -> (!connectedSystem.getSession().getClosed() && (null != activeSessions.get(terminalSessionId) && activeSessions.get(terminalSessionId).isOpen()))); @@ -174,7 +174,7 @@ public void processTerminalMessage( try { String command = terminalMessage.getCommand(); - log.trace("Got " + command); + log.info("Got " + command); Integer keyCode = null; Double keyCodeDbl = terminalMessage.getKeycode(); @@ -196,7 +196,7 @@ public void processTerminalMessage( sessionTrackingService.addTrigger(terminalSessionId, terminalSessionId.getTerminalAuditor().getCurrentTrigger()); } if (keyCode != null && keyCode != -1) { - log.trace("key code isn't null " + keyCode ); + log.info("key code isn't null " + keyCode ); if (keyMap.containsKey(keyCode)) { if (keyCode == 13 @@ -230,7 +230,7 @@ public void processTerminalMessage( } } else { - log.trace("Appending"); + log.info("Appending"); terminalSessionId.getTerminalAuditor().append(command); terminalSessionId.getCommander().print(command); diff --git a/api/src/main/java/io/sentrius/sso/websocket/TerminalWSHandler.java b/api/src/main/java/io/sentrius/sso/websocket/TerminalWSHandler.java index 3134df09..cd313333 100644 --- a/api/src/main/java/io/sentrius/sso/websocket/TerminalWSHandler.java +++ b/api/src/main/java/io/sentrius/sso/websocket/TerminalWSHandler.java @@ -3,8 +3,11 @@ import io.sentrius.sso.automation.auditing.Trigger; import io.sentrius.sso.automation.auditing.TriggerAction; +import io.sentrius.sso.core.model.chat.ChatLog; import io.sentrius.sso.core.model.metadata.TerminalSessionMetadata; +import io.sentrius.sso.core.services.ChatService; import io.sentrius.sso.core.services.metadata.TerminalSessionMetadataService; +import io.sentrius.sso.core.utils.StringUtils; import io.sentrius.sso.protobuf.Session; import io.sentrius.sso.core.security.service.CryptoService; import io.sentrius.sso.core.services.terminal.SessionTrackingService; @@ -36,7 +39,7 @@ public class TerminalWSHandler extends TextWebSocketHandler { final SshListenerService sshListenerService; final CryptoService cryptoService; final TerminalSessionMetadataService terminalSessionMetadataService; - + private final ChatService chatService; // Store active sessions, using session ID or a custom identifier private final ConcurrentHashMap sessions = new ConcurrentHashMap<>(); @@ -113,6 +116,10 @@ protected void handleTextMessage(WebSocketSession session, TextMessage message) sys.getTerminalAuditor().setSessionTrigger(trigger.get()); sessionTrackingService.addSystemTrigger(sys, trigger.get()); } else if (trigger.get().getAction() == TriggerAction.PROMPT_ACTION) { + if (!StringUtils.isBlank( trigger.get().getAsk())){ + // send the question into the log + chatService.save(ChatLog.builder().sender("agent").message(trigger.get().getAsk()).build()); + } sessionTrackingService.addTrigger(sys, trigger.get()); return; } diff --git a/api/src/main/java/io/sentrius/sso/websocket/WebSocketConfig.java b/api/src/main/java/io/sentrius/sso/websocket/WebSocketConfig.java index e2aeca86..031f4a89 100644 --- a/api/src/main/java/io/sentrius/sso/websocket/WebSocketConfig.java +++ b/api/src/main/java/io/sentrius/sso/websocket/WebSocketConfig.java @@ -13,6 +13,7 @@ public class WebSocketConfig implements WebSocketConfigurer { private final TerminalWSHandler customWebSocketHandler; private final AuditSocketHandler auditSocketHandler; + private final ChatWSHandler chatWSHandler; @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(customWebSocketHandler, "/api/v1/ssh/terminal/subscribe") @@ -21,6 +22,9 @@ public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(auditSocketHandler, "/api/v1/audit/attach/subscribe") .setAllowedOriginPatterns("*") .withSockJS(); // SockJS fallback if needed + registry.addHandler(chatWSHandler, "/api/v1/chat/attach/subscribe") + .setAllowedOriginPatterns("*") + .withSockJS(); // SockJS fallback if needed } } \ No newline at end of file diff --git a/api/src/main/resources/db/migration/V17__chat_logs.sql b/api/src/main/resources/db/migration/V17__chat_logs.sql new file mode 100644 index 00000000..bcd96e81 --- /dev/null +++ b/api/src/main/resources/db/migration/V17__chat_logs.sql @@ -0,0 +1,10 @@ +CREATE TABLE IF NOT EXISTS chat_log ( + id BIGSERIAL PRIMARY KEY, + session_id BIGINT NOT NULL, + chat_group_id VARCHAR NOT NULL, -- Unique identifier for different chat dialogs within the session + instance_id INTEGER, + sender VARCHAR NOT NULL, -- username or system (e.g., AI agent) + message TEXT NOT NULL, + message_tm TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (session_id) REFERENCES session_log(id) ON DELETE CASCADE + ); diff --git a/api/src/main/resources/db/migration/V18_approval_history.sql b/api/src/main/resources/db/migration/V18_approval_history.sql new file mode 100644 index 00000000..be35a79c --- /dev/null +++ b/api/src/main/resources/db/migration/V18_approval_history.sql @@ -0,0 +1,21 @@ +ALTER TABLE ztat_approvals ADD COLUMN rationale TEXT; +ALTER TABLE ops_approvals ADD COLUMN rationale TEXT; + +CREATE TABLE IF NOT EXISTS ztat_uses ( + id BIGSERIAL PRIMARY KEY, + ztat_approval_id BIGINT NOT NULL, + user_id BIGINT NOT NULL, + used_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (ztat_approval_id) REFERENCES ztat_approvals(id), + FOREIGN KEY (user_id) REFERENCES users(id) + ); + + +CREATE TABLE IF NOT EXISTS ops_uses ( + id BIGSERIAL PRIMARY KEY, + ops_approval_id BIGINT NOT NULL, + user_id BIGINT NOT NULL, + used_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (ops_approval_id) REFERENCES ops_approvals(id), + FOREIGN KEY (user_id) REFERENCES users(id) + ); diff --git a/api/src/main/resources/static/js/bundled_session_pb.js b/api/src/main/resources/static/js/bundled_session_pb.js index 660cff58..47069fc7 100644 --- a/api/src/main/resources/static/js/bundled_session_pb.js +++ b/api/src/main/resources/static/js/bundled_session_pb.js @@ -1,4 +1,4 @@ -(function(){function p(o,i,f){var a="function"==typeof require&&require;function c(n,r){if(!i[n]){if(!o[n]){var u="function"==typeof require&&require,u;if(!r&&u)return u(n,!0);if(a)return a(n,!0);throw(u=new Error("Cannot find module '"+n+"'")).code="MODULE_NOT_FOUND",u}var u=i[n]={exports:{}};o[n][0].call(u.exports,function(r){var e;return c(o[n][1][r]||r)},u,u.exports,p,o,i,f)}return i[n].exports}for(var r=0;r, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.io.sentrius.protobuf.ChatMessage.prototype.toObject = function(opt_includeInstance) { + return proto.io.sentrius.protobuf.ChatMessage.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.io.sentrius.protobuf.ChatMessage} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.io.sentrius.protobuf.ChatMessage.toObject = function(includeInstance, msg) { + var f, obj = { + sessionId: jspb.Message.getFieldWithDefault(msg, 1, 0), + chatGroupId: jspb.Message.getFieldWithDefault(msg, 2, ""), + sender: jspb.Message.getFieldWithDefault(msg, 3, ""), + message: jspb.Message.getFieldWithDefault(msg, 4, ""), + timestamp: jspb.Message.getFieldWithDefault(msg, 5, 0) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.io.sentrius.protobuf.ChatMessage} + */ +proto.io.sentrius.protobuf.ChatMessage.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.io.sentrius.protobuf.ChatMessage; + return proto.io.sentrius.protobuf.ChatMessage.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.io.sentrius.protobuf.ChatMessage} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.io.sentrius.protobuf.ChatMessage} + */ +proto.io.sentrius.protobuf.ChatMessage.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {number} */ (reader.readInt64()); + msg.setSessionId(value); + break; + case 2: + var value = /** @type {string} */ (reader.readString()); + msg.setChatGroupId(value); + break; + case 3: + var value = /** @type {string} */ (reader.readString()); + msg.setSender(value); + break; + case 4: + var value = /** @type {string} */ (reader.readString()); + msg.setMessage(value); + break; + case 5: + var value = /** @type {number} */ (reader.readInt64()); + msg.setTimestamp(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.io.sentrius.protobuf.ChatMessage.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.io.sentrius.protobuf.ChatMessage.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.io.sentrius.protobuf.ChatMessage} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.io.sentrius.protobuf.ChatMessage.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getSessionId(); + if (f !== 0) { + writer.writeInt64( + 1, + f + ); + } + f = message.getChatGroupId(); + if (f.length > 0) { + writer.writeString( + 2, + f + ); + } + f = message.getSender(); + if (f.length > 0) { + writer.writeString( + 3, + f + ); + } + f = message.getMessage(); + if (f.length > 0) { + writer.writeString( + 4, + f + ); + } + f = message.getTimestamp(); + if (f !== 0) { + writer.writeInt64( + 5, + f + ); + } +}; + + +/** + * optional int64 session_id = 1; + * @return {number} + */ +proto.io.sentrius.protobuf.ChatMessage.prototype.getSessionId = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 1, 0)); +}; + + +/** + * @param {number} value + * @return {!proto.io.sentrius.protobuf.ChatMessage} returns this + */ +proto.io.sentrius.protobuf.ChatMessage.prototype.setSessionId = function(value) { + return jspb.Message.setProto3IntField(this, 1, value); +}; + + +/** + * optional string chat_group_id = 2; + * @return {string} + */ +proto.io.sentrius.protobuf.ChatMessage.prototype.getChatGroupId = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** + * @param {string} value + * @return {!proto.io.sentrius.protobuf.ChatMessage} returns this + */ +proto.io.sentrius.protobuf.ChatMessage.prototype.setChatGroupId = function(value) { + return jspb.Message.setProto3StringField(this, 2, value); +}; + + +/** + * optional string sender = 3; + * @return {string} + */ +proto.io.sentrius.protobuf.ChatMessage.prototype.getSender = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "")); +}; + + +/** + * @param {string} value + * @return {!proto.io.sentrius.protobuf.ChatMessage} returns this + */ +proto.io.sentrius.protobuf.ChatMessage.prototype.setSender = function(value) { + return jspb.Message.setProto3StringField(this, 3, value); +}; + + +/** + * optional string message = 4; + * @return {string} + */ +proto.io.sentrius.protobuf.ChatMessage.prototype.getMessage = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 4, "")); +}; + + +/** + * @param {string} value + * @return {!proto.io.sentrius.protobuf.ChatMessage} returns this + */ +proto.io.sentrius.protobuf.ChatMessage.prototype.setMessage = function(value) { + return jspb.Message.setProto3StringField(this, 4, value); +}; + + +/** + * optional int64 timestamp = 5; + * @return {number} + */ +proto.io.sentrius.protobuf.ChatMessage.prototype.getTimestamp = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 5, 0)); +}; + + +/** + * @param {number} value + * @return {!proto.io.sentrius.protobuf.ChatMessage} returns this + */ +proto.io.sentrius.protobuf.ChatMessage.prototype.setTimestamp = function(value) { + return jspb.Message.setProto3IntField(this, 5, value); +}; + + /** * @enum {number} */ diff --git a/api/src/main/resources/static/js/session_pb.js b/api/src/main/resources/static/js/session_pb.js index 7fd41804..8e273368 100644 --- a/api/src/main/resources/static/js/session_pb.js +++ b/api/src/main/resources/static/js/session_pb.js @@ -12,6 +12,7 @@ var jspb = require('google-protobuf'); var goog = jspb; var global = Function('return this')(); +goog.exportSymbol('proto.io.sentrius.protobuf.ChatMessage', null, global); goog.exportSymbol('proto.io.sentrius.protobuf.MessageType', null, global); goog.exportSymbol('proto.io.sentrius.protobuf.TerminalMessage', null, global); goog.exportSymbol('proto.io.sentrius.protobuf.Trigger', null, global); @@ -58,6 +59,27 @@ if (goog.DEBUG && !COMPILED) { */ proto.io.sentrius.protobuf.TerminalMessage.displayName = 'proto.io.sentrius.protobuf.TerminalMessage'; } +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.io.sentrius.protobuf.ChatMessage = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.io.sentrius.protobuf.ChatMessage, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.io.sentrius.protobuf.ChatMessage.displayName = 'proto.io.sentrius.protobuf.ChatMessage'; +} @@ -519,6 +541,256 @@ proto.io.sentrius.protobuf.TerminalMessage.prototype.setPrompt = function(value) }; + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.io.sentrius.protobuf.ChatMessage.prototype.toObject = function(opt_includeInstance) { + return proto.io.sentrius.protobuf.ChatMessage.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.io.sentrius.protobuf.ChatMessage} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.io.sentrius.protobuf.ChatMessage.toObject = function(includeInstance, msg) { + var f, obj = { + sessionId: jspb.Message.getFieldWithDefault(msg, 1, 0), + chatGroupId: jspb.Message.getFieldWithDefault(msg, 2, ""), + sender: jspb.Message.getFieldWithDefault(msg, 3, ""), + message: jspb.Message.getFieldWithDefault(msg, 4, ""), + timestamp: jspb.Message.getFieldWithDefault(msg, 5, 0) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.io.sentrius.protobuf.ChatMessage} + */ +proto.io.sentrius.protobuf.ChatMessage.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.io.sentrius.protobuf.ChatMessage; + return proto.io.sentrius.protobuf.ChatMessage.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.io.sentrius.protobuf.ChatMessage} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.io.sentrius.protobuf.ChatMessage} + */ +proto.io.sentrius.protobuf.ChatMessage.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {number} */ (reader.readInt64()); + msg.setSessionId(value); + break; + case 2: + var value = /** @type {string} */ (reader.readString()); + msg.setChatGroupId(value); + break; + case 3: + var value = /** @type {string} */ (reader.readString()); + msg.setSender(value); + break; + case 4: + var value = /** @type {string} */ (reader.readString()); + msg.setMessage(value); + break; + case 5: + var value = /** @type {number} */ (reader.readInt64()); + msg.setTimestamp(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.io.sentrius.protobuf.ChatMessage.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.io.sentrius.protobuf.ChatMessage.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.io.sentrius.protobuf.ChatMessage} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.io.sentrius.protobuf.ChatMessage.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getSessionId(); + if (f !== 0) { + writer.writeInt64( + 1, + f + ); + } + f = message.getChatGroupId(); + if (f.length > 0) { + writer.writeString( + 2, + f + ); + } + f = message.getSender(); + if (f.length > 0) { + writer.writeString( + 3, + f + ); + } + f = message.getMessage(); + if (f.length > 0) { + writer.writeString( + 4, + f + ); + } + f = message.getTimestamp(); + if (f !== 0) { + writer.writeInt64( + 5, + f + ); + } +}; + + +/** + * optional int64 session_id = 1; + * @return {number} + */ +proto.io.sentrius.protobuf.ChatMessage.prototype.getSessionId = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 1, 0)); +}; + + +/** + * @param {number} value + * @return {!proto.io.sentrius.protobuf.ChatMessage} returns this + */ +proto.io.sentrius.protobuf.ChatMessage.prototype.setSessionId = function(value) { + return jspb.Message.setProto3IntField(this, 1, value); +}; + + +/** + * optional string chat_group_id = 2; + * @return {string} + */ +proto.io.sentrius.protobuf.ChatMessage.prototype.getChatGroupId = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** + * @param {string} value + * @return {!proto.io.sentrius.protobuf.ChatMessage} returns this + */ +proto.io.sentrius.protobuf.ChatMessage.prototype.setChatGroupId = function(value) { + return jspb.Message.setProto3StringField(this, 2, value); +}; + + +/** + * optional string sender = 3; + * @return {string} + */ +proto.io.sentrius.protobuf.ChatMessage.prototype.getSender = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "")); +}; + + +/** + * @param {string} value + * @return {!proto.io.sentrius.protobuf.ChatMessage} returns this + */ +proto.io.sentrius.protobuf.ChatMessage.prototype.setSender = function(value) { + return jspb.Message.setProto3StringField(this, 3, value); +}; + + +/** + * optional string message = 4; + * @return {string} + */ +proto.io.sentrius.protobuf.ChatMessage.prototype.getMessage = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 4, "")); +}; + + +/** + * @param {string} value + * @return {!proto.io.sentrius.protobuf.ChatMessage} returns this + */ +proto.io.sentrius.protobuf.ChatMessage.prototype.setMessage = function(value) { + return jspb.Message.setProto3StringField(this, 4, value); +}; + + +/** + * optional int64 timestamp = 5; + * @return {number} + */ +proto.io.sentrius.protobuf.ChatMessage.prototype.getTimestamp = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 5, 0)); +}; + + +/** + * @param {number} value + * @return {!proto.io.sentrius.protobuf.ChatMessage} returns this + */ +proto.io.sentrius.protobuf.ChatMessage.prototype.setTimestamp = function(value) { + return jspb.Message.setProto3IntField(this, 5, value); +}; + + /** * @enum {number} */ diff --git a/api/src/main/resources/templates/sso/enclaves/edit_enclave.html b/api/src/main/resources/templates/sso/enclaves/edit_enclave.html index 44da6077..15bad99c 100755 --- a/api/src/main/resources/templates/sso/enclaves/edit_enclave.html +++ b/api/src/main/resources/templates/sso/enclaves/edit_enclave.html @@ -141,6 +141,17 @@

Edit Enclave Configuration

placeholder="Enter Max Concurrent Sessions" th:value="${hostGroup.configuration.maxConcurrentSessions}" /> +
+ + +
+ +
+ + +
+ + + + + [[${systemOptions.systemLogoName}]] - Your account is locked + + + + + + + + +
+
+

Your account is locked

+
+ + + + diff --git a/api/src/main/resources/templates/sso/logout.html b/api/src/main/resources/templates/sso/logout.html deleted file mode 100755 index 6d948c3f..00000000 --- a/api/src/main/resources/templates/sso/logout.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - [[${systemOptions.systemLogoName}]] - You have been logged out - - - - -
-

-
- You have been logged out! Please click login to enter the system again.
- If you are using a shared computer, you should close the browser to ensure that you are logged out.
- Please note that if your system is using cert based auth you will be logged back in automatically when clicking the login link. -
- - - \ No newline at end of file diff --git a/api/src/main/resources/templates/sso/ssh/secure_shell.html b/api/src/main/resources/templates/sso/ssh/sso.html similarity index 82% rename from api/src/main/resources/templates/sso/ssh/secure_shell.html rename to api/src/main/resources/templates/sso/ssh/sso.html index b82b6e36..9b2e28f4 100755 --- a/api/src/main/resources/templates/sso/ssh/secure_shell.html +++ b/api/src/main/resources/templates/sso/ssh/sso.html @@ -3,12 +3,95 @@ +