diff --git a/ccm-core/pom.xml b/ccm-core/pom.xml
index 8c09637e8..eb0104aa0 100644
--- a/ccm-core/pom.xml
+++ b/ccm-core/pom.xml
@@ -92,6 +92,12 @@
flyway-core
+
+
org.apache.logging.log4j
diff --git a/ccm-core/src/main/java/org/libreccm/core/api/CcmCoreApi.java b/ccm-core/src/main/java/org/libreccm/api/AdminApi.java
similarity index 73%
rename from ccm-core/src/main/java/org/libreccm/core/api/CcmCoreApi.java
rename to ccm-core/src/main/java/org/libreccm/api/AdminApi.java
index e75fc3b26..9e9b7b837 100644
--- a/ccm-core/src/main/java/org/libreccm/core/api/CcmCoreApi.java
+++ b/ccm-core/src/main/java/org/libreccm/api/AdminApi.java
@@ -3,11 +3,10 @@
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
-package org.libreccm.core.api;
+package org.libreccm.api;
-import org.libreccm.api.CorsFilter;
-import org.libreccm.api.DefaultResponseHeaders;
-import org.libreccm.api.PreflightRequestFilter;
+import org.libreccm.security.GroupsApi;
+import org.libreccm.security.RolesApi;
import org.libreccm.security.UsersApi;
import java.util.HashSet;
@@ -20,17 +19,22 @@ import javax.ws.rs.core.Application;
*
* @author Jens Pelzetter
*/
-@ApplicationPath("/api")
-public class CcmCoreApi extends Application {
+@ApplicationPath("/api/admin")
+public class AdminApi extends Application {
@Override
public Set> getClasses() {
-
final Set> classes = new HashSet<>();
+
classes.add(CorsFilter.class);
classes.add(DefaultResponseHeaders.class);
classes.add(PreflightRequestFilter.class);
+
+ classes.add(GroupsApi.class);
+ classes.add(RolesApi.class);
classes.add(UsersApi.class);
+
+
return classes;
}
diff --git a/ccm-core/src/main/java/org/libreccm/security/GroupsApi.java b/ccm-core/src/main/java/org/libreccm/security/GroupsApi.java
new file mode 100644
index 000000000..8660cc866
--- /dev/null
+++ b/ccm-core/src/main/java/org/libreccm/security/GroupsApi.java
@@ -0,0 +1,466 @@
+/*
+ * Copyright (C) 2020 LibreCCM Foundation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */package org.libreccm.security;
+
+import org.libreccm.core.CoreConstants;
+import org.libreccm.core.api.ExtractedIdentifier;
+import org.libreccm.core.api.IdentifierExtractor;
+import org.libreccm.core.api.JsonArrayCollector;
+
+import java.net.URI;
+
+import javax.enterprise.context.RequestScoped;
+import javax.inject.Inject;
+import javax.json.JsonArray;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.transaction.Transactional;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+/**
+ *
+ * @author Jens Pelzetter
+ */
+@RequestScoped
+@Path("/groups")
+public class GroupsApi {
+
+ @Inject
+ private IdentifierExtractor identifierExtractor;
+
+ @Inject
+ private GroupManager groupManager;
+
+ @Inject
+ private GroupRepository groupRepository;
+
+ @Inject
+ private RoleManager roleManager;
+
+ @Inject
+ private RoleRepository roleRepository;
+
+ @Inject
+ private UserManager userManager;
+
+ @Inject
+ private UserRepository userRepository;
+
+ @GET
+ @Path("/")
+ @Produces(MediaType.APPLICATION_JSON)
+ @AuthorizationRequired
+ @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
+ @Transactional(Transactional.TxType.REQUIRED)
+ public JsonArray getGroups(
+ @QueryParam("limit") @DefaultValue("20") final int limit,
+ @QueryParam("offset") @DefaultValue("0") final int offset
+ ) {
+ return groupRepository
+ .findAll(limit, offset)
+ .stream()
+ .map(Group::buildJson)
+ .map(JsonObjectBuilder::build)
+ .collect(new JsonArrayCollector());
+ }
+
+ @GET
+ @Path("/{groupIdentifier}")
+ @Produces(MediaType.APPLICATION_JSON)
+ @AuthorizationRequired
+ @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
+ @Transactional(Transactional.TxType.REQUIRED)
+ public JsonObject getGroup(
+ @PathParam("groupIdentifier") final String identifierParam
+ ) {
+ return findGroup(identifierParam).toJson();
+ }
+
+ @POST
+ @Path("/")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @AuthorizationRequired
+ @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
+ @Transactional(Transactional.TxType.REQUIRED)
+ public Response addGroup(final JsonObject groupData) {
+ if (groupData.isNull("name")
+ || groupData.getString("name").matches("\\s*")) {
+ return Response
+ .status(Response.Status.BAD_REQUEST)
+ .entity("Name of new group is missing.")
+ .build();
+ }
+ final String name = groupData.getString("name");
+
+ if (groupRepository.findByName(name).isPresent()) {
+ return Response
+ .status(Response.Status.CONFLICT)
+ .entity(
+ String.format(
+ "A group with the name %s already exists.",
+ name
+ )
+ )
+ .build();
+ }
+
+ final Group group = new Group();
+ group.setName(name);
+ groupRepository.save(group);
+
+ return Response
+ .status(Response.Status.CREATED)
+ .contentLocation(
+ URI.create(String.format("/api/groups/%s", group.getName()))
+ )
+ .build();
+ }
+
+ @PUT
+ @Path("/{groupIdentifier}")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @AuthorizationRequired
+ @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
+ @Transactional(Transactional.TxType.REQUIRED)
+ public Response updateGroup(
+ @PathParam("groupIdentifier") final String groupIdentifier,
+ final JsonObject groupData
+ ) {
+ final Group group = findGroup(groupIdentifier);
+
+ boolean updated = false;
+ if (!groupData.isNull("name")
+ && !groupData.getString("name").matches("\\s*")
+ && !groupData.getString("name").equals(group.getName())) {
+ group.setName(groupData.getString("name"));
+ updated = true;
+ }
+
+ if (updated) {
+ groupRepository.save(group);
+ }
+
+ return Response
+ .status(Response.Status.OK)
+ .entity(
+ String.format(
+ "Group %s updated successfully.", group.getName()
+ )
+ )
+ .build();
+ }
+
+ @DELETE
+ @Path("/{groupIdentifier}")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @AuthorizationRequired
+ @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
+ @Transactional(Transactional.TxType.REQUIRED)
+ public Response deleteGroup(
+ @PathParam("groupIdentifier") final String groupIdentifier
+ ) {
+ final Group group = findGroup(groupIdentifier);
+ final String name = group.getName();
+ groupRepository.delete(group);
+ return Response
+ .ok(String.format("Group %s deleted successfully.", name))
+ .build();
+ }
+
+ @GET
+ @Path("/{groupIdentifier}/members")
+ @Produces(MediaType.APPLICATION_JSON)
+ @AuthorizationRequired
+ @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
+ @Transactional(Transactional.TxType.REQUIRED)
+ public JsonArray getMembers(
+ @PathParam("groupIdentifier") final String groupIdentifier
+ ) {
+ return findGroup(groupIdentifier)
+ .getMemberships()
+ .stream()
+ .map(GroupMembership::toJson)
+ .collect(new JsonArrayCollector());
+ }
+
+ @PUT
+ @Path("/{groupIdentifier}/members/{userIdentifier}")
+ @AuthorizationRequired
+ @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
+ @Transactional(Transactional.TxType.REQUIRED)
+ public Response addMember(
+ @PathParam("groupIdentifier") final String groupIdentifier,
+ @PathParam("userIdentifier") final String userIdentifier
+ ) {
+ final Group group = findGroup(groupIdentifier);
+ final User user = findUser(userIdentifier);
+
+ groupManager.addMemberToGroup(user, group);
+
+ return Response
+ .ok(
+ String.format(
+ "User %s successfully added to group %s.",
+ user.getName(),
+ group.getName()
+ )
+ ).build();
+
+ }
+
+ @DELETE
+ @Path("/{groupIdentifier}/members/{userIdentifier}")
+ @AuthorizationRequired
+ @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
+ @Transactional(Transactional.TxType.REQUIRED)
+ public Response removeMember(
+ @PathParam("groupIdentifier") final String groupIdentifier,
+ @PathParam("userIdentifier") final String userIdentifier
+ ) {
+ final Group group = findGroup(groupIdentifier);
+ final User user = findUser(userIdentifier);
+
+ groupManager.removeMemberFromGroup(user, group);
+
+ return Response
+ .ok()
+ .entity(
+ String.format(
+ "User %s successfully removed to group %s.",
+ user.getName(),
+ group.getName()
+ )
+ )
+ .build();
+ }
+
+ @GET
+ @Path("/{groupIdentifier}/roles")
+ @Produces(MediaType.APPLICATION_JSON)
+ @AuthorizationRequired
+ @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
+ @Transactional(Transactional.TxType.REQUIRED)
+ public JsonArray getRoleMemberships(
+ @PathParam("groupIdentifier")
+ final String groupIdentifier
+ ) {
+ return findGroup(groupIdentifier)
+ .getRoleMemberships()
+ .stream()
+ .map(RoleMembership::toJson)
+ .collect(new JsonArrayCollector());
+ }
+
+ @PUT
+ @Path("/{groupIdentifier}/groups/{roleIdentifier}")
+ @AuthorizationRequired
+ @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
+ @Transactional(Transactional.TxType.REQUIRED)
+ public Response addRoleMembership(
+ @PathParam("groupIdentifier")
+ final String groupIdentifier,
+ @PathParam("roleIdentifier")
+ final String roleIdentifier
+ ) {
+ final Group group = findGroup(groupIdentifier);
+ final Role role = findRole(roleIdentifier);
+
+ roleManager.assignRoleToParty(role, group);
+
+ return Response
+ .ok()
+ .entity(
+ String.format(
+ "Role %s successfully assigned to group %s.",
+ role.getName(),
+ group.getName()
+ )
+ )
+ .build();
+ }
+
+ @DELETE
+ @Path("/{groupIdentifier}/groups/{roleIdentifier}")
+ @AuthorizationRequired
+ @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
+ @Transactional(Transactional.TxType.REQUIRED)
+ public Response removeRoleMembership(
+ @PathParam("groupIdentifier")
+ final String groupIdentifier,
+ @PathParam("roleIdentifier")
+ final String roleIdentifier
+ ) {
+ final Group group = findGroup(groupIdentifier);
+ final Role role = findRole(roleIdentifier);
+
+ roleManager.removeRoleFromParty(role, group);
+
+ return Response
+ .ok()
+ .entity(
+ String.format(
+ "Role %s successfully removed from group %s.",
+ role.getName(),
+ group.getName()
+ )
+ )
+ .build();
+ }
+
+ private Group findGroup(final String groupIdentifier) {
+ final ExtractedIdentifier identifier = identifierExtractor
+ .extractIdentifier(groupIdentifier);
+
+ switch (identifier.getType()) {
+ case ID:
+ return groupRepository
+ .findById(Long.parseLong(identifier.getIdentifier()))
+ .orElseThrow(
+ () -> new WebApplicationException(
+ String.format(
+ "No group with ID %s found",
+ identifier.getIdentifier()
+ ),
+ Response.Status.NOT_FOUND
+ )
+ );
+ case UUID:
+ return groupRepository
+ .findByUuid(identifier.getIdentifier())
+ .orElseThrow(
+ () -> new WebApplicationException(
+ String.format(
+ "No group with UUID %s found.",
+ identifier.getIdentifier()
+ ),
+ Response.Status.NOT_FOUND
+ )
+ );
+ default:
+ return groupRepository
+ .findByName(identifier.getIdentifier())
+ .orElseThrow(
+ () -> new WebApplicationException(
+ String.format(
+ "No group with name %s found.",
+ identifier.getIdentifier()
+ ),
+ Response.Status.NOT_FOUND
+ )
+ );
+ }
+ }
+
+ private Role findRole(final String roleIdentifier) {
+ final ExtractedIdentifier identifier = identifierExtractor
+ .extractIdentifier(roleIdentifier);
+
+ switch (identifier.getType()) {
+ case ID:
+ return roleRepository
+ .findById(Long.parseLong(identifier.getIdentifier()))
+ .orElseThrow(
+ () -> new WebApplicationException(
+ String.format(
+ "No role with ID %s found.",
+ identifier.getIdentifier()
+ ),
+ Response.Status.NOT_FOUND
+ )
+ );
+ case UUID:
+ return roleRepository
+ .findByUuid(identifier.getIdentifier())
+ .orElseThrow(
+ () -> new WebApplicationException(
+ String.format(
+ "No role with UUID %s found.",
+ identifier.getIdentifier()
+ ),
+ Response.Status.NOT_FOUND
+ )
+ );
+ default:
+ return roleRepository
+ .findByName(identifier.getIdentifier())
+ .orElseThrow(
+ () -> new WebApplicationException(
+ String.format(
+ "No role with name %s found.",
+ identifier.getIdentifier()
+ ),
+ Response.Status.NOT_FOUND
+ )
+ );
+ }
+ }
+
+ private User findUser(final String identifierParam) {
+ final ExtractedIdentifier identifier = identifierExtractor
+ .extractIdentifier(identifierParam);
+
+ switch (identifier.getType()) {
+ case ID:
+ return userRepository
+ .findById(Long.parseLong(identifier.getIdentifier()))
+ .orElseThrow(
+ () -> new WebApplicationException(
+ String.format(
+ "No user with ID %s found.",
+ identifier.getIdentifier()
+ ),
+ Response.Status.NOT_FOUND)
+ );
+ case UUID:
+ return userRepository
+ .findByUuid(identifier.getIdentifier())
+ .orElseThrow(
+ () -> new WebApplicationException(
+ String.format(
+ "No user with UUID %s found.",
+ identifier.getIdentifier()
+ ),
+ Response.Status.NOT_FOUND)
+ );
+ default:
+ return userRepository
+ .findByName(identifier.getIdentifier())
+ .orElseThrow(
+ () -> new WebApplicationException(
+ String.format(
+ "No user with name %s found.",
+ identifier.getIdentifier()
+ ),
+ Response.Status.NOT_FOUND)
+ );
+ }
+ }
+
+}
diff --git a/ccm-core/src/main/java/org/libreccm/security/Role.java b/ccm-core/src/main/java/org/libreccm/security/Role.java
index c1b56f900..2a98a695e 100644
--- a/ccm-core/src/main/java/org/libreccm/security/Role.java
+++ b/ccm-core/src/main/java/org/libreccm/security/Role.java
@@ -22,7 +22,6 @@ import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import org.hibernate.search.annotations.Field;
-import org.hibernate.validator.constraints.NotBlank;
import org.libreccm.l10n.LocalizedString;
import org.libreccm.workflow.TaskAssignment;
@@ -57,6 +56,7 @@ import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.Table;
+import javax.validation.constraints.NotBlank;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
diff --git a/ccm-core/src/main/java/org/libreccm/security/RolesApi.java b/ccm-core/src/main/java/org/libreccm/security/RolesApi.java
new file mode 100644
index 000000000..33c680603
--- /dev/null
+++ b/ccm-core/src/main/java/org/libreccm/security/RolesApi.java
@@ -0,0 +1,19 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package org.libreccm.security;
+
+import javax.enterprise.context.RequestScoped;
+import javax.ws.rs.Path;
+
+/**
+ *
+ * @author Jens Pelzetter
+ */
+@RequestScoped
+@Path("/roles")
+public class RolesApi {
+
+}
diff --git a/ccm-core/src/main/java/org/libreccm/security/UsersApi.java b/ccm-core/src/main/java/org/libreccm/security/UsersApi.java
index 9f437438a..2d433da28 100644
--- a/ccm-core/src/main/java/org/libreccm/security/UsersApi.java
+++ b/ccm-core/src/main/java/org/libreccm/security/UsersApi.java
@@ -46,6 +46,8 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
/**
+ * Provides RESTful API endpoints for managing users. Access to all endpoints
+ * defined by this class requires admin privileges.
*
* @author Jens Pelzetter
*/
@@ -74,6 +76,14 @@ public class UsersApi {
@Inject
private UserRepository userRepository;
+ /**
+ * Retrieves all users.
+ *
+ * @param limit How many users should be retrieved?
+ * @param offset The first user to retrieve
+ *
+ * @return A JSON array with the all users.
+ */
@GET
@Path("/")
@Produces(MediaType.APPLICATION_JSON)
@@ -92,6 +102,13 @@ public class UsersApi {
.collect(new JsonArrayCollector());
}
+ /**
+ * Retrieves a specific user.
+ *
+ * @param identifierParam The identifier for the user.
+ *
+ * @return A JSON representation of the user.
+ */
@GET
@Path("/{userIdentifier}")
@Produces(MediaType.APPLICATION_JSON)
@@ -104,6 +121,19 @@ public class UsersApi {
return findUser(identifierParam).toJson();
}
+ /**
+ * Creates a new user.
+ *
+ * @param userData The data from which the user is generated.
+ *
+ * @return A HTTP response indicating the success or failure of the user
+ * generation. A 201 (Created) response code is send if the user was
+ * created successfully together with the URL of the new user. If
+ * the provided data does not contain a required value a response
+ * with the 400 (Bad Request) status code is returned. If a user
+ * with the name provided by the user data already exists a response
+ * with a 409 (Conflict) response is returned.
+ */
@POST
@Path("/")
@Consumes(MediaType.APPLICATION_JSON)
@@ -111,7 +141,6 @@ public class UsersApi {
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public Response addUser(final JsonObject userData) {
-
final String givenName;
if (userData.isNull("givenName")) {
givenName = null;
@@ -188,6 +217,16 @@ public class UsersApi {
.build();
}
+ /**
+ * Updates a user.
+ *
+ * @param userIdentifier The identifier of the user to update.
+ * @param userData The updated of the user. Please note that the name
+ * of a user can't be updated. If the user data
+ * contains a value for this property it is ignored.
+ *
+ * @return A 200 (OK) response if the user is succesfully updated.
+ */
@PUT
@Path("/{userIdentifier}")
@Consumes(MediaType.APPLICATION_JSON)
@@ -195,8 +234,7 @@ public class UsersApi {
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public Response updateUser(
- @PathParam("userIdentifier")
- final String userIdentifier,
+ @PathParam("userIdentifier") final String userIdentifier,
final JsonObject userData
) {
final User user = findUser(userIdentifier);
@@ -236,6 +274,12 @@ public class UsersApi {
.build();
}
+ /**
+ * Deletes a user.
+ *
+ * @param userIdentifier The identifier of the user to delete.
+ * @return A 200 (OK) response if the user was deleted successfully.
+ */
@DELETE
@Path("/{userIdentifier}")
@Consumes(MediaType.APPLICATION_JSON)
@@ -243,8 +287,7 @@ public class UsersApi {
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public Response deleteUser(
- @PathParam("userIdentifier")
- final String userIdentifier
+ @PathParam("userIdentifier") final String userIdentifier
) {
final User user = findUser(userIdentifier);
final String name = user.getName();
@@ -262,8 +305,7 @@ public class UsersApi {
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public JsonArray getGroupMemberships(
- @PathParam("userIdentifier")
- final String userIdentifier
+ @PathParam("userIdentifier") final String userIdentifier
) {
return findUser(userIdentifier)
.getGroupMemberships()
@@ -278,10 +320,8 @@ public class UsersApi {
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public Response addGroupMembership(
- @PathParam("userIdentifier")
- final String userIdentifier,
- @PathParam("groupIdentifier")
- final String groupIdentifier
+ @PathParam("userIdentifier") final String userIdentifier,
+ @PathParam("groupIdentifier") final String groupIdentifier
) {
final User user = findUser(userIdentifier);
final Group group = findGroup(groupIdentifier);
@@ -306,10 +346,8 @@ public class UsersApi {
@RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN)
@Transactional(Transactional.TxType.REQUIRED)
public Response removeGroupMembership(
- @PathParam("userIdentifier")
- final String userIdentifier,
- @PathParam("groupIdentifier")
- final String groupIdentifier
+ @PathParam("userIdentifier") final String userIdentifier,
+ @PathParam("groupIdentifier") final String groupIdentifier
) {
final User user = findUser(userIdentifier);
final Group group = findGroup(groupIdentifier);
@@ -320,7 +358,7 @@ public class UsersApi {
.ok()
.entity(
String.format(
- "User %s successfully removed to group %s.",
+ "User %s successfully removed from group %s.",
user.getName(),
group.getName()
)
@@ -358,9 +396,9 @@ public class UsersApi {
) {
final User user = findUser(userIdentifier);
final Role role = findRole(roleIdentifier);
-
+
roleManager.assignRoleToParty(role, user);
-
+
return Response
.ok()
.entity(
@@ -386,9 +424,9 @@ public class UsersApi {
) {
final User user = findUser(userIdentifier);
final Role role = findRole(roleIdentifier);
-
+
roleManager.removeRoleFromParty(role, user);
-
+
return Response
.ok()
.entity(
diff --git a/pom.xml b/pom.xml
index f3cbc5039..614a3152d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -720,6 +720,23 @@
woodstox-core-asl
4.4.1
+
+
+
+ io.swagger.core.v3
+ swagger-jaxrs2
+ 2.1.2
+
+
+ io.swagger.core.v3
+ swagger-jaxrs2-servlet-initializer-v2
+ 2.1.2
+
+
+ io.swagger.core.v3
+ swagger-annotations
+ 2.1.2
+