diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/AuthFilter.java b/src/java/org/jivesoftware/openfire/plugin/rest/AuthFilter.java index ce447d5c4..a4197b416 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/AuthFilter.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/AuthFilter.java @@ -25,6 +25,7 @@ import javax.ws.rs.container.PreMatching; import javax.ws.rs.core.Context; import javax.ws.rs.core.Response.Status; +import javax.ws.rs.core.SecurityContext; import org.jivesoftware.openfire.XMPPServer; import org.jivesoftware.openfire.admin.AdminManager; @@ -32,10 +33,12 @@ import org.jivesoftware.openfire.auth.ConnectionException; import org.jivesoftware.openfire.auth.InternalUnauthenticatedException; import org.jivesoftware.openfire.auth.UnauthorizedException; +import org.jivesoftware.util.JiveGlobals; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; +import java.security.Principal; /** * The Class AuthFilter. @@ -55,47 +58,23 @@ public class AuthFilter implements ContainerRequestFilter { private RESTServicePlugin plugin = (RESTServicePlugin) XMPPServer.getInstance().getPluginManager() .getPlugin("restapi"); - @Override - public void filter(ContainerRequestContext containerRequest) throws IOException { - if (containerRequest.getUriInfo().getRequestUri().getPath().equals("/plugins/restapi/v1/openapi.yaml")) { - LOG.debug("Authentication was bypassed for openapi.yaml file (documentation)"); - return; - } + public static final String SHARED_SECRET_AUTHENTICATION_SCHEME = "SharedSecret"; - if (isStatusEndpoint(containerRequest.getUriInfo().getRequestUri().getPath())) { - LOG.debug("Authentication was bypassed for a status endpoint"); - return; - } + @Override + public void filter(ContainerRequestContext requestContext) throws IOException { if (!plugin.isEnabled()) { LOG.debug("REST API Plugin is not enabled"); throw new WebApplicationException(Status.FORBIDDEN); } - - // Let the preflight request through the authentication - if ("OPTIONS".equals(containerRequest.getMethod())) { - LOG.debug("Authentication was bypassed because of OPTIONS request"); - return; - } - - // To be backwards compatible to userservice 1.* - if (containerRequest.getUriInfo().getRequestUri().getPath().contains("restapi/v1/userservice")) { - LOG.info("Deprecated 'userservice' endpoint was used. Please switch to the new endpoints"); + + if (!authRequired(requestContext)){ return; } if (!plugin.getAllowedIPs().isEmpty()) { // Get client's IP address - String ipAddress = httpRequest.getHeader("x-forwarded-for"); - if (ipAddress == null) { - ipAddress = httpRequest.getHeader("X_FORWARDED_FOR"); - if (ipAddress == null) { - ipAddress = httpRequest.getHeader("X-Forward-For"); - if (ipAddress == null) { - ipAddress = httpRequest.getRemoteAddr(); - } - } - } + String ipAddress = getClientIPAddressForRequest(httpRequest); if (!plugin.getAllowedIPs().contains(ipAddress)) { LOG.warn("REST API rejected service for IP address: " + ipAddress); throw new WebApplicationException(Status.UNAUTHORIZED); @@ -103,9 +82,11 @@ public void filter(ContainerRequestContext containerRequest) throws IOException } // Get the authentication passed in HTTP headers parameters - String auth = containerRequest.getHeaderString("authorization"); + String auth = requestContext.getHeaderString("authorization"); if (auth == null) { + LOG.warn("REST API request with no Authorization header rejected. [Request IP: {}, Request URI: {}]", + getClientIPAddressForRequest(httpRequest), requestContext.getUriInfo().getRequestUri().getPath()); throw new WebApplicationException(Status.UNAUTHORIZED); } @@ -115,41 +96,120 @@ public void filter(ContainerRequestContext containerRequest) throws IOException // If username or password fail if (usernameAndPassword == null || usernameAndPassword.length != 2) { - LOG.warn("Username or password is not set"); - throw new WebApplicationException(Status.UNAUTHORIZED); + LOG.warn("Basic authentication failed. Username or password is not set. [Request IP: {}, Request URI: {}]", + getClientIPAddressForRequest(httpRequest), requestContext.getUriInfo().getRequestUri().getPath()); + throw new WebApplicationException("Username or password is not set", Status.UNAUTHORIZED); } boolean userAdmin = AdminManager.getInstance().isUserAdmin(usernameAndPassword[0], true); if (!userAdmin) { LOG.warn("Provided User is not an admin"); - throw new WebApplicationException(Status.UNAUTHORIZED); + throw new WebApplicationException("User is not authorised", Status.UNAUTHORIZED); } try { AuthFactory.authenticate(usernameAndPassword[0], usernameAndPassword[1]); + setSecurityForContext(requestContext, usernameAndPassword[0], SecurityContext.BASIC_AUTH); + if (JiveGlobals.getBooleanProperty(RESTServicePlugin.SERVICE_LOGGING_ENABLED, false)) { + LOG.info("Authentication - successfully authenticated user. [Request IP: {}, Request URI: {}, Username: {}]", + getClientIPAddressForRequest(httpRequest), requestContext.getUriInfo().getRequestUri().getPath(), usernameAndPassword[0]); + } } catch (UnauthorizedException e) { - LOG.warn("Wrong HTTP Basic Auth authorization", e); - throw new WebApplicationException(Status.UNAUTHORIZED); - } catch (ConnectionException e) { - LOG.error("Authentication went wrong", e); - throw new WebApplicationException(Status.UNAUTHORIZED); - } catch (InternalUnauthenticatedException e) { + LOG.warn("Basic authentication failed. Username or password is incorrect. [Request IP: {}, Request URI: {}]", + getClientIPAddressForRequest(httpRequest), requestContext.getUriInfo().getRequestUri().getPath()); + LOG.warn("Authentication error", e); + throw new WebApplicationException("Username or password is incorrect", Status.UNAUTHORIZED); + } catch (ConnectionException | InternalUnauthenticatedException e) { LOG.error("Authentication went wrong", e); throw new WebApplicationException(Status.UNAUTHORIZED); } } else { if (!auth.equals(plugin.getSecret())) { - LOG.warn("Wrong secret key authorization. Provided key: " + auth); + LOG.warn("Wrong secret key authorization. [Request IP: {}, Request URI: {}, Request auth: {}]", + getClientIPAddressForRequest(httpRequest), requestContext.getUriInfo().getRequestUri().getPath(), auth); throw new WebApplicationException(Status.UNAUTHORIZED); + } else { + //For shared secret, use the authentication scheme to indicate that a username is unknown + setSecurityForContext(requestContext, SHARED_SECRET_AUTHENTICATION_SCHEME, SHARED_SECRET_AUTHENTICATION_SCHEME); + if (JiveGlobals.getBooleanProperty(RESTServicePlugin.SERVICE_LOGGING_ENABLED, false)) { + LOG.info("Authentication - successfully authenticated by secret key. [Request IP: {}, Request URI: {}]", + getClientIPAddressForRequest(httpRequest), requestContext.getUriInfo().getRequestUri().getPath()); + } } } } + private boolean authRequired(ContainerRequestContext requestContext){ + if (requestContext.getUriInfo().getRequestUri().getPath().equals("/plugins/restapi/v1/openapi.yaml")) { + LOG.debug("Authentication was bypassed for openapi.yaml file (documentation)"); + return false; + } + + if (isStatusEndpoint(requestContext.getUriInfo().getRequestUri().getPath())) { + LOG.debug("Authentication was bypassed for a status endpoint"); + return false; + } + + // Let the preflight request through the authentication + if ("OPTIONS".equals(requestContext.getMethod())) { + LOG.debug("Authentication was bypassed because of OPTIONS request"); + return false; + } + + // To be backwards compatible to userservice 1.* + if (requestContext.getUriInfo().getRequestUri().getPath().contains("restapi/v1/userservice")) { + LOG.info("Deprecated 'userservice' endpoint was used. Please switch to the new endpoints"); + return false; + } + + return true; + } + + private void setSecurityForContext(ContainerRequestContext requestContext, String username, String authScheme){ + final SecurityContext currentSecurityContext = requestContext.getSecurityContext(); + requestContext.setSecurityContext(new SecurityContext() { + + @Override + public Principal getUserPrincipal() { + return () -> username; + } + + @Override + public boolean isUserInRole(String role) { + return true; + } + + @Override + public boolean isSecure() { + return currentSecurityContext.isSecure(); + } + + @Override + public String getAuthenticationScheme() { + return authScheme; + } + }); + } + private boolean isStatusEndpoint(String path){ return path.equals("/plugins/restapi/v1/system/liveness") || path.startsWith("/plugins/restapi/v1/system/liveness/") || path.equals("/plugins/restapi/v1/system/readiness") || path.startsWith("/plugins/restapi/v1/system/readiness/"); } + + private String getClientIPAddressForRequest(HttpServletRequest request) { + String ipAddress = request.getHeader("x-forwarded-for"); + if (ipAddress == null) { + ipAddress = request.getHeader("X_FORWARDED_FOR"); + if (ipAddress == null) { + ipAddress = request.getHeader("X-Forward-For"); + if (ipAddress == null) { + ipAddress = request.getRemoteAddr(); + } + } + } + return ipAddress; + } } diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/controller/ClusteringController.java b/src/java/org/jivesoftware/openfire/plugin/rest/controller/ClusteringController.java index 75a9ef062..442691976 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/controller/ClusteringController.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/controller/ClusteringController.java @@ -18,12 +18,10 @@ import org.jivesoftware.openfire.cluster.ClusterManager; import org.jivesoftware.openfire.cluster.ClusterNodeInfo; import org.jivesoftware.openfire.cluster.NodeID; -import org.jivesoftware.openfire.plugin.rest.RESTServicePlugin; import org.jivesoftware.openfire.plugin.rest.entity.ClusterNodeEntities; import org.jivesoftware.openfire.plugin.rest.entity.ClusterNodeEntity; -import org.jivesoftware.util.JiveGlobals; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.jivesoftware.openfire.plugin.rest.utils.LoggingUtils; +import org.jivesoftware.openfire.plugin.rest.utils.LoggingUtils.AuditEvent; import java.nio.charset.StandardCharsets; import java.util.Collection; @@ -31,8 +29,6 @@ import java.util.stream.Collectors; public class ClusteringController { - private static final Logger LOG = LoggerFactory.getLogger(ClusteringController.class); - private static ClusteringController INSTANCE = null; /** @@ -56,13 +52,8 @@ public static void setInstance(final ClusteringController instance) { ClusteringController.INSTANCE = instance; } - public static void log(String logMessage) { - if (JiveGlobals.getBooleanProperty(RESTServicePlugin.SERVICE_LOGGING_ENABLED, false)) { - LOG.info(logMessage); - } - } - public String getClusterStatus() { + LoggingUtils.auditEvent(AuditEvent.CLUSTERING_GET_STATUS); if (ClusterManager.isClusteringEnabled()) { if (ClusterManager.isClusteringStarted()) { if (ClusterManager.isSeniorClusterMember()) { @@ -83,11 +74,13 @@ public String getClusterStatus() { } public Optional getNodeEntity(String nodeId) { + LoggingUtils.auditEvent(AuditEvent.CLUSTERING_GET_NODE, nodeId); final Optional nodeInfo = ClusterManager.getNodeInfo(NodeID.getInstance(nodeId.getBytes(StandardCharsets.UTF_8))); return nodeInfo.map(ClusterNodeEntity::from); } public ClusterNodeEntities getNodeEntities() { + LoggingUtils.auditEvent(AuditEvent.CLUSTERING_GET_NODES); final Collection nodesInfo = ClusterManager.getNodesInfo(); return new ClusterNodeEntities(nodesInfo.stream().map(ClusterNodeEntity::from).collect(Collectors.toList())); } diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/controller/GroupController.java b/src/java/org/jivesoftware/openfire/plugin/rest/controller/GroupController.java index 4a36d36b6..0eda7404f 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/controller/GroupController.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/controller/GroupController.java @@ -28,6 +28,7 @@ import org.jivesoftware.openfire.plugin.rest.entity.GroupEntity; import org.jivesoftware.openfire.plugin.rest.exceptions.ExceptionType; import org.jivesoftware.openfire.plugin.rest.exceptions.ServiceException; +import org.jivesoftware.openfire.plugin.rest.utils.LoggingUtils; import org.jivesoftware.openfire.plugin.rest.utils.MUCRoomUtils; import org.xmpp.packet.JID; @@ -55,6 +56,7 @@ public static GroupController getInstance() { * the service exception */ public List getGroups() throws ServiceException { + LoggingUtils.auditEvent(LoggingUtils.AuditEvent.GROUPS_LIST); Collection groups = GroupManager.getInstance().getGroups(); List groupEntities = new ArrayList<>(); for (Group group : groups) { @@ -75,6 +77,7 @@ public List getGroups() throws ServiceException { * the service exception */ public GroupEntity getGroup(String groupName) throws ServiceException { + LoggingUtils.auditEvent(LoggingUtils.AuditEvent.GROUPS_GET_BY_NAME, groupName); Group group; try { group = GroupManager.getInstance().getGroup(groupName); @@ -101,6 +104,7 @@ public GroupEntity getGroup(String groupName) throws ServiceException { * the service exception */ public Group createGroup(GroupEntity groupEntity) throws ServiceException { + LoggingUtils.auditEvent(LoggingUtils.AuditEvent.GROUPS_CREATE, groupEntity); Group group; if (groupEntity != null && !groupEntity.getName().isEmpty()) { try { @@ -163,6 +167,7 @@ public Group createGroup(GroupEntity groupEntity) throws ServiceException { * @throws ServiceException the service exception */ public Group updateGroup(String groupName, GroupEntity groupEntity) throws ServiceException { + LoggingUtils.auditEvent(LoggingUtils.AuditEvent.GROUPS_UPDATE_BY_NAME, groupName, groupEntity); Group group; if (groupEntity != null && !groupEntity.getName().isEmpty()) { if (groupName.equals(groupEntity.getName())) { @@ -251,6 +256,7 @@ public Group updateGroup(String groupName, GroupEntity groupEntity) throws Servi * the service exception */ public void deleteGroup(String groupName) throws ServiceException { + LoggingUtils.auditEvent(LoggingUtils.AuditEvent.GROUPS_DELETE, groupName); try { Group group = GroupManager.getInstance().getGroup(groupName); GroupManager.getInstance().deleteGroup(group); diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/controller/JustMarriedController.java b/src/java/org/jivesoftware/openfire/plugin/rest/controller/JustMarriedController.java index 2953de4ff..8df764024 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/controller/JustMarriedController.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/controller/JustMarriedController.java @@ -30,6 +30,7 @@ import org.jivesoftware.openfire.lockout.LockOutManager; import org.jivesoftware.openfire.plugin.rest.exceptions.ExceptionType; import org.jivesoftware.openfire.plugin.rest.exceptions.ServiceException; +import org.jivesoftware.openfire.plugin.rest.utils.LoggingUtils; import org.jivesoftware.openfire.roster.Roster; import org.jivesoftware.openfire.roster.RosterItem; import org.jivesoftware.openfire.session.ClientSession; @@ -66,6 +67,13 @@ public static boolean changeName(String currentUserName, String newUserName, boo String newEmail, String newRealName) throws ServiceException { UserManager userManager = UserManager.getInstance(); + LoggingUtils.auditEvent(LoggingUtils.AuditEvent.USER_CHANGE_NAME, + "currentUserName", currentUserName, + "newUserName", newUserName, + "deleteOldUser", deleteOldUser, + "newEmail", newEmail, + "newRealName", newRealName); + try { User currentUser = userManager.getUser(currentUserName); // Old user found, create new one diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/controller/MUCRoomController.java b/src/java/org/jivesoftware/openfire/plugin/rest/controller/MUCRoomController.java index 57b07d850..0e3d97aca 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/controller/MUCRoomController.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/controller/MUCRoomController.java @@ -26,6 +26,7 @@ import org.jivesoftware.openfire.plugin.rest.entity.*; import org.jivesoftware.openfire.plugin.rest.exceptions.ExceptionType; import org.jivesoftware.openfire.plugin.rest.exceptions.ServiceException; +import org.jivesoftware.openfire.plugin.rest.utils.LoggingUtils; import org.jivesoftware.openfire.plugin.rest.utils.MUCRoomUtils; import org.jivesoftware.openfire.plugin.rest.utils.UserUtils; import org.jivesoftware.util.AlreadyExistsException; @@ -154,6 +155,11 @@ protected static MUCRoom getRoom(@Nonnull final String serviceName, @Nonnull fin public MUCRoomEntities getChatRooms(String serviceName, String channelType, String roomSearch, boolean expand) throws ServiceException { log("Get the chat rooms"); + LoggingUtils.auditEvent(LoggingUtils.AuditEvent.MUC_LIST_ROOMS, + "serviceName", serviceName, + "channelType", channelType, + "roomSearch", roomSearch, + "expand", expand); final MultiUserChatService service = MUCServiceController.getService(serviceName); Collection roomsInfo = service.getAllRoomSearchInfo(); @@ -196,7 +202,10 @@ public MUCRoomEntities getChatRooms(String serviceName, String channelType, Stri * the service exception */ public MUCRoomEntity getChatRoom(String roomName, String serviceName, boolean expand) throws ServiceException { - log("Get the chat room: " + roomName); + LoggingUtils.auditEvent(LoggingUtils.AuditEvent.MUC_GET_ROOM, + "roomName", roomName, + "serviceName", serviceName, + "expand", expand); final MUCRoom chatRoom = getRoom(serviceName, roomName); return convertToMUCRoomEntity(chatRoom, expand); } @@ -212,7 +221,9 @@ public MUCRoomEntity getChatRoom(String roomName, String serviceName, boolean ex * the service exception */ public void deleteChatRoom(String roomName, String serviceName) throws ServiceException { - log("Delete the chat room: " + roomName); + LoggingUtils.auditEvent(LoggingUtils.AuditEvent.MUC_DELETE_ROOM, + "roomName", roomName, + "serviceName", serviceName); final MUCRoom chatRoom = getRoom(serviceName, roomName); chatRoom.destroyRoom(null, null); } @@ -227,6 +238,9 @@ public void deleteChatRoom(String roomName, String serviceName) throws ServiceEx */ public void createChatRoom(String serviceName, MUCRoomEntity mucRoomEntity, boolean sendInvitations) throws ServiceException { log("Create a chat room: " + mucRoomEntity.getRoomName()); + LoggingUtils.auditEvent(LoggingUtils.AuditEvent.MUC_CREATE_ROOM, + "serviceName", serviceName, + "mucRoomEntity", mucRoomEntity); try { createRoom(mucRoomEntity, serviceName, sendInvitations); } catch (NotAllowedException | ForbiddenException e) { @@ -296,7 +310,10 @@ public RoomCreationResultEntities createMultipleChatRooms(String serviceName, MU */ public void updateChatRoom(String roomName, String serviceName, MUCRoomEntity mucRoomEntity, boolean sendInvitations) throws ServiceException { - log("Update a chat room: " + mucRoomEntity.getRoomName()); + LoggingUtils.auditEvent(LoggingUtils.AuditEvent.MUC_UPDATE_ROOM, + "roomName", roomName, + "serviceName", serviceName, + "mucRoomEntity", mucRoomEntity); try { // If the room name is different throw exception if (!JID.nodeprep(roomName).equals(mucRoomEntity.getRoomName())) { @@ -493,7 +510,9 @@ private boolean equalToAffiliations(MUCRoom room, MUCRoomEntity mucRoomEntity) { */ public ParticipantEntities getRoomParticipants(String roomName, String serviceName) throws ServiceException { - log("Get room participants for room: " + roomName); + LoggingUtils.auditEvent(LoggingUtils.AuditEvent.MUC_GET_PARTICIPANT_LIST, + "roomName", roomName, + "serviceName", serviceName); ParticipantEntities participantEntities = new ParticipantEntities(); List participants = new ArrayList<>(); @@ -523,7 +542,9 @@ public ParticipantEntities getRoomParticipants(String roomName, String serviceNa */ public OccupantEntities getRoomOccupants(String roomName, String serviceName) throws ServiceException { - log("Get room occupants for room: " + roomName); + LoggingUtils.auditEvent(LoggingUtils.AuditEvent.MUC_GET_OCCUPANT_LIST, + "roomName", roomName, + "serviceName", serviceName); OccupantEntities occupantEntities = new OccupantEntities(); List occupants = new ArrayList<>(); @@ -553,7 +574,9 @@ public OccupantEntities getRoomOccupants(String roomName, String serviceName) th * @return the room chat history */ public MUCRoomMessageEntities getRoomHistory(String roomName, String serviceName) throws ServiceException { - log("Get room history for room: " + roomName); + LoggingUtils.auditEvent(LoggingUtils.AuditEvent.MUC_GET_ROOM_HISTORY, + "roomName", roomName, + "serviceName", serviceName); MUCRoomMessageEntities mucRoomMessageEntities = new MUCRoomMessageEntities(); List listMessages = new ArrayList<>(); @@ -602,6 +625,10 @@ public MUCRoomMessageEntities getRoomHistory(String roomName, String serviceName */ public void inviteUsersAndOrGroups(String serviceName, String roomName, MUCInvitationsEntity mucInvitationsEntity) throws ServiceException { + LoggingUtils.auditEvent(LoggingUtils.AuditEvent.MUC_INVITE_USER, + "roomName", roomName, + "serviceName", serviceName, + "invitation" , mucInvitationsEntity); MUCRoom room = getRoom(serviceName, roomName); // First determine where to send all the invitations @@ -987,6 +1014,7 @@ private static Collection setRoles(MUCRoom room, MUCRoomEntity mucRoomEntit */ public Collection getByAffiliation(@Nonnull final String serviceName, @Nonnull final String roomName, @Nonnull final MUCRole.Affiliation affiliation) throws ServiceException { + LoggingUtils.auditEvent(LoggingUtils.AuditEvent.MUC_LIST_AFFILIATED_USERS_FOR_AFFILIATION, serviceName, roomName, affiliation); final MUCRoom room = getRoom(serviceName, roomName); switch (affiliation) { case admin: @@ -1025,6 +1053,7 @@ public Collection getByAffiliation(@Nonnull final String serviceName, @Nonn */ public void replaceAffiliatedUsers(@Nonnull final String serviceName, @Nonnull final String roomName, @Nonnull final MUCRole.Affiliation affiliation, boolean sendInvitations, @Nonnull final String... jids) throws ServiceException { + LoggingUtils.auditEvent(LoggingUtils.AuditEvent.MUC_REPLACE_AFFILIATED_USERS_FOR_AFFILIATION, serviceName, roomName, affiliation, jids); final Collection replacements = new HashSet<>(); // Input validation. @@ -1110,6 +1139,7 @@ public void replaceAffiliatedUsers(@Nonnull final String serviceName, @Nonnull f */ public void addAffiliatedUsers(@Nonnull final String serviceName, @Nonnull final String roomName, @Nonnull final MUCRole.Affiliation affiliation, boolean sendInvitations, @Nonnull final String... jids) throws ServiceException { + LoggingUtils.auditEvent(LoggingUtils.AuditEvent.MUC_ADD_AFFILIATED_USERS_FOR_AFFILIATION, serviceName, roomName, affiliation, jids); final Collection additions = new HashSet<>(); // Input validation. @@ -1183,6 +1213,7 @@ public void addAffiliatedUsers(@Nonnull final String serviceName, @Nonnull final * the service exception */ public void deleteAffiliation(String serviceName, String roomName, MUCRole.Affiliation affiliation, String jid) throws ServiceException { + LoggingUtils.auditEvent(LoggingUtils.AuditEvent.MUC_REMOVE_AFFILIATED_USER_OR_GROUP_FOR_AFFILIATION, serviceName, roomName, jid); MUCRoom room = getRoom(serviceName, roomName); try { JID userJid = UserUtils.checkAndGetJID(jid); diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/controller/MessageController.java b/src/java/org/jivesoftware/openfire/plugin/rest/controller/MessageController.java index 25e16fbb4..305ba597d 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/controller/MessageController.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/controller/MessageController.java @@ -22,6 +22,7 @@ import org.jivesoftware.openfire.plugin.rest.entity.MessageEntity; import org.jivesoftware.openfire.plugin.rest.exceptions.ExceptionType; import org.jivesoftware.openfire.plugin.rest.exceptions.ServiceException; +import org.jivesoftware.openfire.plugin.rest.utils.LoggingUtils; /** * The Class MessageController. @@ -48,6 +49,7 @@ public static MessageController getInstance() { * the service exception */ public void sendBroadcastMessage(MessageEntity messageEntity) throws ServiceException { + LoggingUtils.auditEvent(LoggingUtils.AuditEvent.MESSAGE_BROADCAST, messageEntity); if (messageEntity.getBody() != null && !messageEntity.getBody().isEmpty()) { SessionManager.getInstance().sendServerMessage(null, messageEntity.getBody()); } else { diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/controller/MsgArchiveController.java b/src/java/org/jivesoftware/openfire/plugin/rest/controller/MsgArchiveController.java index 41536ee11..6bab0ee41 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/controller/MsgArchiveController.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/controller/MsgArchiveController.java @@ -22,6 +22,7 @@ import java.sql.SQLException; import org.jivesoftware.database.DbConnectionManager; +import org.jivesoftware.openfire.plugin.rest.utils.LoggingUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xmpp.packet.JID; @@ -64,6 +65,7 @@ private MsgArchiveController() { * @return the total number of user unread messages. */ public int getUnReadMessagesCount(JID jid) { + LoggingUtils.auditEvent(LoggingUtils.AuditEvent.MESSAGE_ARCHIVE_UNREAD_COUNT, jid); int messageCount = 0; Connection con = null; PreparedStatement pstmt = null; diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/entity/GroupEntity.java b/src/java/org/jivesoftware/openfire/plugin/rest/entity/GroupEntity.java index 277e3448f..b54b70981 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/entity/GroupEntity.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/entity/GroupEntity.java @@ -19,9 +19,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Schema; - import java.util.List; -import java.util.Optional; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementWrapper; @@ -172,4 +170,17 @@ public void setMembers(List members) { */ public void setShared(Boolean shared) { this.shared = shared; } + + @Override + public String toString() { + + return "GroupEntity [" + + "name='" + name + + ", description='" + description + + ", admins=" + admins + + ", members=" + members + + ", shared=" + shared + + "]"; + } + } diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/entity/MUCInvitationEntity.java b/src/java/org/jivesoftware/openfire/plugin/rest/entity/MUCInvitationEntity.java index 907daa31c..84de75129 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/entity/MUCInvitationEntity.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/entity/MUCInvitationEntity.java @@ -38,4 +38,8 @@ public String getReason() { public void setReason(String reason) { this.reason = reason; } + @Override + public String toString() { + return "MUCInvitationEntity [reason=" + reason + "]"; + } } diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/entity/MUCRoomEntity.java b/src/java/org/jivesoftware/openfire/plugin/rest/entity/MUCRoomEntity.java index e846ac042..86e95a170 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/entity/MUCRoomEntity.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/entity/MUCRoomEntity.java @@ -363,4 +363,32 @@ public void setAdminGroups(List adminGroups) { this.adminGroups = adminGroups; } + @Override + public String toString() { + return "MUCRoomEntity [" + + "roomName=" + roomName + + ", naturalName=" + naturalName + + ", description=" + description + + ", persistent=" + persistent + + ", publicRoom=" + publicRoom + + ", canAnyoneDiscoverJID=" + canAnyoneDiscoverJID + + ", canOccupantsChangeSubject=" + canOccupantsChangeSubject + + ", canOccupantsInvite=" + canOccupantsInvite + + ", canChangeNickname=" + canChangeNickname + + ", logEnabled=" + logEnabled + + ", loginRestrictedToNickname=" + loginRestrictedToNickname + + ", membersOnly=" + membersOnly + + ", moderated=" + moderated + + ", registrationEnabled=" + registrationEnabled + + ", broadcastPresenceRoles=" + broadcastPresenceRoles + + ", owners=" + owners + + ", ownerGroups=" + ownerGroups + + ", members=" + members + + ", memberGroups=" + memberGroups + + ", outcasts=" + outcasts + + ", outcastGroups=" + outcastGroups + + ", admins=" + admins + + ", adminGroups=" + adminGroups + + "]"; + } } diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/entity/MessageEntity.java b/src/java/org/jivesoftware/openfire/plugin/rest/entity/MessageEntity.java index 3458cc1af..1acbc6ac2 100644 --- a/src/java/org/jivesoftware/openfire/plugin/rest/entity/MessageEntity.java +++ b/src/java/org/jivesoftware/openfire/plugin/rest/entity/MessageEntity.java @@ -53,4 +53,9 @@ public String getBody() { public void setBody(String body) { this.body = body; } + + @Override + public String toString() { + return "MessageEntity [body=" + body + "]"; + } } diff --git a/src/java/org/jivesoftware/openfire/plugin/rest/utils/LoggingUtils.java b/src/java/org/jivesoftware/openfire/plugin/rest/utils/LoggingUtils.java new file mode 100644 index 000000000..aa789caf0 --- /dev/null +++ b/src/java/org/jivesoftware/openfire/plugin/rest/utils/LoggingUtils.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2022 Ignite Realtime Foundation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.openfire.plugin.rest.utils; + +import org.eclipse.jetty.util.log.Log; +import org.jivesoftware.openfire.plugin.rest.RESTServicePlugin; +import org.jivesoftware.util.JiveGlobals; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; + +public class LoggingUtils { + private static final Logger AUDIT_LOG = LoggerFactory.getLogger("RestAPI-Plugin-Audit"); + private static final Logger LOG = LoggerFactory.getLogger(LoggingUtils.class); + + public enum AuditEvent { + //Clustering + CLUSTERING_GET_STATUS, + CLUSTERING_GET_NODES, + CLUSTERING_GET_NODE, + + //Groups + GROUPS_LIST, + GROUPS_GET_BY_NAME, + GROUPS_CREATE, + GROUPS_UPDATE_BY_NAME, + GROUPS_DELETE, + + //JustMarried + USER_CHANGE_NAME, + + //Messages + MESSAGE_BROADCAST, + + //Message Archive + MESSAGE_ARCHIVE_UNREAD_COUNT, + + //MUC + // - MUC Affiliations + MUC_LIST_AFFILIATED_USERS_FOR_AFFILIATION, + MUC_REPLACE_AFFILIATED_USERS_FOR_AFFILIATION, + MUC_REMOVE_AFFILIATED_USER_OR_GROUP_FOR_AFFILIATION, + MUC_ADD_AFFILIATED_USERS_FOR_AFFILIATION, + MUC_ADD_AFFILIATED_USER_OR_GROUP_AS_ADMIN, + MUC_ADD_AFFILIATED_USER_OR_GROUP_AS_MEMBER, + MUC_ADD_AFFILIATED_USER_OR_GROUP_AS_OUTCAST, + MUC_ADD_AFFILIATED_USER_OR_GROUP_AS_OWNER, + // - MUC Rooms + MUC_LIST_ROOMS, + MUC_GET_ROOM, + MUC_DELETE_ROOM, + MUC_CREATE_ROOM, + MUC_UPDATE_ROOM, + MUC_GET_PARTICIPANT_LIST, + MUC_GET_OCCUPANT_LIST, + MUC_GET_ROOM_HISTORY, + MUC_INVITE_USER, + ; + } + + public static void auditEvent(AuditEvent event){ + auditEvent(event, ""); + } + + public static void auditEvent(AuditEvent event, Object... parameters){ + if (JiveGlobals.getBooleanProperty(RESTServicePlugin.SERVICE_LOGGING_ENABLED, false)) { + String parameterString = parseParameters(parameters); + String logMessage = "Event: " + event; + logMessage += " - "; + logMessage += "Parameters: + " + parameterString; + logMessage += " - "; + logMessage += "Caller: " + getCaller(); + AUDIT_LOG.info(logMessage); + }; + } + + private static String parseParameters(Object[] parameters) { + //TODO: Does this belong here? + ArrayList parsed = new ArrayList<>(); + for (Object obj: parameters) { + if(obj == null){ + parsed.add("null"); + } else { + try { + parsed.add(obj.toString()); + } catch (Exception e) { + parsed.add("unparseable"); + } + } + } + return parsed.toString(); + } + + /* + * Returns the name and method of the calling class. + */ + private static String getCaller() { + try { + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + for (StackTraceElement element : stackTrace) { + if(element.getClassName().equals(LoggingUtils.class.getName())){ + continue; + } + return element.getClassName() + "." + element.getMethodName(); + } + } catch (Exception e) { + LOG.error("Unable to get caller of the logger. This should be impossible.", e); + } + return "unknown"; + } +}