From 0a03224761e8f414cad363fa79238356595d35da Mon Sep 17 00:00:00 2001 From: Jens Pelzetter Date: Mon, 11 May 2020 20:33:18 +0200 Subject: [PATCH] Started implementation of RESTful API for administrative tasks --- .gitignore | 1 + .../java/org/libreccm/api/CorsFilter.java | 52 +++++++ .../libreccm/api/DefaultResponseHeaders.java | 43 ++++++ .../libreccm/api/PreflightRequestFilter.java | 58 ++++++++ .../java/org/libreccm/api/package-info.java | 23 +++ .../core/AbstractEntityRepository.java | 31 ++++- .../java/org/libreccm/core/EmailAddress.java | 12 ++ .../org/libreccm/core/api/ApiConstants.java | 34 +++++ .../org/libreccm/core/api/CcmCoreApi.java | 39 ++++++ .../core/api/ExtractedIdentifier.java | 46 ++++++ .../core/api/IdentifierExtractor.java | 51 +++++++ .../org/libreccm/core/api/IdentifierType.java | 31 +++++ .../libreccm/core/api/JsonArrayCollector.java | 67 +++++++++ .../libreccm/security/GroupMembership.java | 30 +++- .../java/org/libreccm/security/Party.java | 39 +++++- .../org/libreccm/security/RoleMembership.java | 28 +++- .../main/java/org/libreccm/security/User.java | 65 ++++++--- .../java/org/libreccm/security/UsersApi.java | 131 ++++++++++++++++++ 18 files changed, 745 insertions(+), 36 deletions(-) create mode 100644 ccm-core/src/main/java/org/libreccm/api/CorsFilter.java create mode 100644 ccm-core/src/main/java/org/libreccm/api/DefaultResponseHeaders.java create mode 100644 ccm-core/src/main/java/org/libreccm/api/PreflightRequestFilter.java create mode 100644 ccm-core/src/main/java/org/libreccm/api/package-info.java create mode 100644 ccm-core/src/main/java/org/libreccm/core/api/ApiConstants.java create mode 100644 ccm-core/src/main/java/org/libreccm/core/api/CcmCoreApi.java create mode 100644 ccm-core/src/main/java/org/libreccm/core/api/ExtractedIdentifier.java create mode 100644 ccm-core/src/main/java/org/libreccm/core/api/IdentifierExtractor.java create mode 100644 ccm-core/src/main/java/org/libreccm/core/api/IdentifierType.java create mode 100644 ccm-core/src/main/java/org/libreccm/core/api/JsonArrayCollector.java create mode 100644 ccm-core/src/main/java/org/libreccm/security/UsersApi.java diff --git a/.gitignore b/.gitignore index 3e32cc4d3..6c5749265 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +nb-configuration.xml node node_modules runtime diff --git a/ccm-core/src/main/java/org/libreccm/api/CorsFilter.java b/ccm-core/src/main/java/org/libreccm/api/CorsFilter.java new file mode 100644 index 000000000..769941604 --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/api/CorsFilter.java @@ -0,0 +1,52 @@ +/* + * 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.api; + +import java.io.IOException; + +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerResponseContext; +import javax.ws.rs.container.ContainerResponseFilter; + +/** + * A JAX-RS filter adding HTTP headers required for CORS. + * + * @author Jens Pelzetter + */ +public class CorsFilter implements ContainerResponseFilter { + + @Override + public void filter( + final ContainerRequestContext requestContext, + final ContainerResponseContext responseContext) + throws IOException { + if (!requestContext.getMethod().equals("OPTIONS")) { + responseContext.getHeaders().add("Access-Control-Allow-Origin", "*"); + responseContext.getHeaders().add( + "Access-Control-Allow-Headers", "Authorization, Content-Type" + ); + responseContext.getHeaders().add( + "Access-Control-Allow-Methods", + "DELETE, HEAD, GET, OPTIONS, POST, PUT" + ); + responseContext.getHeaders().add("Access-Control-Max-Age", "86400"); + } + } + +} diff --git a/ccm-core/src/main/java/org/libreccm/api/DefaultResponseHeaders.java b/ccm-core/src/main/java/org/libreccm/api/DefaultResponseHeaders.java new file mode 100644 index 000000000..e10a66fac --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/api/DefaultResponseHeaders.java @@ -0,0 +1,43 @@ +/* + * 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.api; + +import java.io.IOException; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.ext.WriterInterceptor; +import javax.ws.rs.ext.WriterInterceptorContext; + +/** + * Add some headers to every response. + * + * @author Jens Pelzetter + */ +public class DefaultResponseHeaders implements WriterInterceptor { + + @Override + public void aroundWriteTo(WriterInterceptorContext context) + throws IOException, WebApplicationException { + context + .getHeaders() + .add("Access-Control-Allow-Origin", "*"); + context.proceed(); + } + +} diff --git a/ccm-core/src/main/java/org/libreccm/api/PreflightRequestFilter.java b/ccm-core/src/main/java/org/libreccm/api/PreflightRequestFilter.java new file mode 100644 index 000000000..b201617a5 --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/api/PreflightRequestFilter.java @@ -0,0 +1,58 @@ +/* + * 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.api; + +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.core.Response; + +/** + * A filter which intercepts {@code OPTION} requests to the API and responds + * with the required headers. + * + * A {@code OPTIONS} request is send by browsers to send to endpoints to prevent + * cross site scripting. + * + * @author Jens Pelzetter + */ +public class PreflightRequestFilter implements ContainerRequestFilter { + + @Override + public void filter(final ContainerRequestContext requestContext) { + if (requestContext.getMethod().equals("OPTIONS")) { + requestContext.abortWith( + Response + .status(Response.Status.NO_CONTENT) + .header("Connection", "keep-alive") + .header("Access-Control-Allow-Origin", "*") + .header( + "Access-Control-Allow-Headers", + "Authorization, Content-Type" + ) + .header( + "Access-Control-Allow-Methods", + "DELETE, HEAD, GET, OPTIONS, POST, PUT" + ) + .header("Access-Control-Max-Age", "86400") + .build() + ); + } + } + +} diff --git a/ccm-core/src/main/java/org/libreccm/api/package-info.java b/ccm-core/src/main/java/org/libreccm/api/package-info.java new file mode 100644 index 000000000..206797864 --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/api/package-info.java @@ -0,0 +1,23 @@ +/* + * 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 + */ + +/** + * Utilility classes for RESTful apis of the modules. + */ +package org.libreccm.api; diff --git a/ccm-core/src/main/java/org/libreccm/core/AbstractEntityRepository.java b/ccm-core/src/main/java/org/libreccm/core/AbstractEntityRepository.java index e33c8b1ea..1f303a273 100644 --- a/ccm-core/src/main/java/org/libreccm/core/AbstractEntityRepository.java +++ b/ccm-core/src/main/java/org/libreccm/core/AbstractEntityRepository.java @@ -135,12 +135,13 @@ public abstract class AbstractEntityRepository implements Serializable { /** * Get the primary key/id of a entity. - * + * * @param entity The entity + * * @return The ID of the provided {@code entity}. */ public abstract K getIdOfEntity(E entity); - + /** * Finds an entity by it ID. * @@ -223,15 +224,15 @@ public abstract class AbstractEntityRepository implements Serializable { return Optional.empty(); } } - + @Transactional(Transactional.TxType.REQUIRED) public E reload(final E entity, final String... fetchJoins) { - + return findById(getIdOfEntity(entity), fetchJoins) .orElseThrow(() -> new IllegalArgumentException(String - .format("No Entity of type \"%s\" with ID %s in the database.", - getEntityClass().getName(), - Objects.toString(getIdOfEntity(entity))))); + .format("No Entity of type \"%s\" with ID %s in the database.", + getEntityClass().getName(), + Objects.toString(getIdOfEntity(entity))))); } /** @@ -248,6 +249,10 @@ public abstract class AbstractEntityRepository implements Serializable { return executeCriteriaQuery(createCriteriaQuery()); } + public List findAll(final int limit, final int offset) { + return executeCriteriaQuery(createCriteriaQuery(), limit, offset); + } + @Transactional(Transactional.TxType.REQUIRED) public List findAll(final String entityGraphName) { @SuppressWarnings("unchecked") @@ -282,6 +287,18 @@ public abstract class AbstractEntityRepository implements Serializable { return query.getResultList(); } + public List executeCriteriaQuery( + final CriteriaQuery criteriaQuery, + final int limit, + final int offset + ) { + return entityManager + .createQuery(criteriaQuery) + .setFirstResult(offset) + .setMaxResults(limit) + .getResultList(); + } + public List executeCriteriaQuery(final CriteriaQuery criteriaQuery, final String graphName) { @SuppressWarnings("unchecked") diff --git a/ccm-core/src/main/java/org/libreccm/core/EmailAddress.java b/ccm-core/src/main/java/org/libreccm/core/EmailAddress.java index 6ac47fbe4..3d012d596 100644 --- a/ccm-core/src/main/java/org/libreccm/core/EmailAddress.java +++ b/ccm-core/src/main/java/org/libreccm/core/EmailAddress.java @@ -25,11 +25,15 @@ import javax.persistence.Column; import javax.persistence.Embeddable; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; + import java.io.Serializable; import java.util.Objects; import static org.libreccm.core.CoreConstants.CORE_XML_NS; +import javax.json.Json; +import javax.json.JsonObjectBuilder; + /** * An embeddable entity for storing email addresses. * @@ -122,6 +126,14 @@ public class EmailAddress implements Serializable { return obj instanceof EmailAddress; } + public JsonObjectBuilder buildJson() { + return Json + .createObjectBuilder() + .add("address", address) + .add("bouncing", bouncing) + .add("verified", verified); + } + @Override public String toString() { return String.format("%s{ " diff --git a/ccm-core/src/main/java/org/libreccm/core/api/ApiConstants.java b/ccm-core/src/main/java/org/libreccm/core/api/ApiConstants.java new file mode 100644 index 000000000..31d6489e1 --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/core/api/ApiConstants.java @@ -0,0 +1,34 @@ +/* + * 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.core.api; + +/** + * + * @author Jens Pelzetter + */ +public final class ApiConstants { + + private ApiConstants() { + // Nothing + } + + public static final String IDENTIFIER_PREFIX_ID = "ID-"; + public static final String IDENTIFIER_PREFIX_UUID = "UUID-"; + +} diff --git a/ccm-core/src/main/java/org/libreccm/core/api/CcmCoreApi.java b/ccm-core/src/main/java/org/libreccm/core/api/CcmCoreApi.java new file mode 100644 index 000000000..4c4802bb7 --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/core/api/CcmCoreApi.java @@ -0,0 +1,39 @@ +/* + * 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.core.api; + +import org.libreccm.api.CorsFilter; +import org.libreccm.api.DefaultResponseHeaders; +import org.libreccm.api.PreflightRequestFilter; +import org.libreccm.security.UsersApi; + +import java.util.HashSet; +import java.util.Set; + +import javax.ws.rs.ApplicationPath; +import javax.ws.rs.core.Application; + +/** + * + * @author Jens Pelzetter + */ +@ApplicationPath("/api/ccm-core") +public class CcmCoreApi 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(UsersApi.class); + return classes; + } + + + +} diff --git a/ccm-core/src/main/java/org/libreccm/core/api/ExtractedIdentifier.java b/ccm-core/src/main/java/org/libreccm/core/api/ExtractedIdentifier.java new file mode 100644 index 000000000..faf20ca54 --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/core/api/ExtractedIdentifier.java @@ -0,0 +1,46 @@ +/* + * 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.core.api; + +/** + * + * @author Jens Pelzetter + */ +public class ExtractedIdentifier { + + private final IdentifierType type; + + private final String identifier; + + protected ExtractedIdentifier( + final IdentifierType type, final String identifier + ) { + this.type = type; + this.identifier = identifier; + } + + public IdentifierType getType() { + return type; + } + + public String getIdentifier() { + return identifier; + } + +} diff --git a/ccm-core/src/main/java/org/libreccm/core/api/IdentifierExtractor.java b/ccm-core/src/main/java/org/libreccm/core/api/IdentifierExtractor.java new file mode 100644 index 000000000..d0894e1d2 --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/core/api/IdentifierExtractor.java @@ -0,0 +1,51 @@ +/* + * 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.core.api; + +import java.util.Objects; + +import javax.enterprise.context.Dependent; + +/** + * + * @author Jens Pelzetter + */ +@Dependent +public class IdentifierExtractor { + + public ExtractedIdentifier extractIdentifier(final String identifierParam) { + Objects.requireNonNull(identifierParam, "identifier param is null."); + + if (identifierParam.startsWith(ApiConstants.IDENTIFIER_PREFIX_ID)) { + final String identifier = identifierParam + .substring(ApiConstants.IDENTIFIER_PREFIX_ID.length()); + return new ExtractedIdentifier(IdentifierType.ID, identifier); + } else if (identifierParam.startsWith( + ApiConstants.IDENTIFIER_PREFIX_UUID)) { + final String identifier = identifierParam + .substring(ApiConstants.IDENTIFIER_PREFIX_UUID.length()); + return new ExtractedIdentifier(IdentifierType.ID, identifier); + } else { + return new ExtractedIdentifier( + IdentifierType.PROPERTY, identifierParam + ); + } + } + +} diff --git a/ccm-core/src/main/java/org/libreccm/core/api/IdentifierType.java b/ccm-core/src/main/java/org/libreccm/core/api/IdentifierType.java new file mode 100644 index 000000000..a2a2b1551 --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/core/api/IdentifierType.java @@ -0,0 +1,31 @@ +/* + * 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.core.api; + +/** + * + * @author Jens Pelzetter + */ +public enum IdentifierType { + + ID, + UUID, + PROPERTY + +} diff --git a/ccm-core/src/main/java/org/libreccm/core/api/JsonArrayCollector.java b/ccm-core/src/main/java/org/libreccm/core/api/JsonArrayCollector.java new file mode 100644 index 000000000..94a9730a6 --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/core/api/JsonArrayCollector.java @@ -0,0 +1,67 @@ +/* + * 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.core.api; + +import com.google.common.collect.Sets; + +import java.util.Collections; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collector; + +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonArrayBuilder; +import javax.json.JsonValue; + +/** + * + * @author Jens Pelzetter + */ +public class JsonArrayCollector implements Collector{ + + @Override + public Supplier supplier() { + return Json::createArrayBuilder; + } + + @Override + public BiConsumer accumulator() { + return JsonArrayBuilder::add; + } + + @Override + public BinaryOperator combiner() { + return (left, right) -> left.add(right); + } + + @Override + public Function finisher() { + return JsonArrayBuilder::build; + } + + @Override + public Set characteristics() { + return Collections.emptySet(); + } + +} diff --git a/ccm-core/src/main/java/org/libreccm/security/GroupMembership.java b/ccm-core/src/main/java/org/libreccm/security/GroupMembership.java index fcb19541f..fe18e4c81 100644 --- a/ccm-core/src/main/java/org/libreccm/security/GroupMembership.java +++ b/ccm-core/src/main/java/org/libreccm/security/GroupMembership.java @@ -34,6 +34,9 @@ import static org.libreccm.core.CoreConstants.DB_SCHEMA; import com.fasterxml.jackson.annotation.ObjectIdGenerators; import org.libreccm.imexport.Exportable; +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; @@ -55,8 +58,7 @@ import javax.persistence.Table; @Table(name = "GROUP_MEMBERSHIPS", schema = DB_SCHEMA) @NamedQueries({ @NamedQuery(name = "GroupMembership.findByUuid", - query = "SELECT m FROM GroupMembership m WHERE m.uuid = :uuid") - , + query = "SELECT m FROM GroupMembership m WHERE m.uuid = :uuid"), @NamedQuery(name = "GroupMembership.findByGroupAndUser", query = "SELECT m FROM GroupMembership m " + "WHERE m.member = :member AND m.group = :group")}) @@ -158,6 +160,30 @@ public class GroupMembership implements Serializable, Exportable { return obj instanceof GroupMembership; } + public JsonObjectBuilder buildJson() { + + return Json + .createObjectBuilder() + .add("membershipId", membershipId) + .add("uuid", uuid) + .add( + "group", + Json + .createObjectBuilder() + .add("partyId", group.getPartyId()) + .add("uuid", group.getUuid()) + .add("name", group.getName()) + ) + .add( + "member", + Json + .createObjectBuilder() + .add("partyId", member.getPartyId()) + .add("uuid", member.getUuid()) + .add("name", member.getName()) + ); + } + @Override public String toString() { return String.format("%s{ " diff --git a/ccm-core/src/main/java/org/libreccm/security/Party.java b/ccm-core/src/main/java/org/libreccm/security/Party.java index e0be03b9c..0cad09b9c 100644 --- a/ccm-core/src/main/java/org/libreccm/security/Party.java +++ b/ccm-core/src/main/java/org/libreccm/security/Party.java @@ -23,6 +23,7 @@ import static org.libreccm.core.CoreConstants.*; import com.fasterxml.jackson.annotation.JsonIdentityInfo; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.ObjectIdGenerators; +import org.libreccm.core.api.JsonArrayCollector; import javax.persistence.FetchType; import javax.validation.constraints.NotNull; @@ -35,6 +36,9 @@ import java.util.HashSet; import java.util.Objects; import java.util.Set; +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonObjectBuilder; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; @@ -67,14 +71,12 @@ import javax.persistence.Table; ), @NamedQuery( name = "Party.findByName", - query = "SELECT p FROM Party p WHERE p.name = :name") - , + query = "SELECT p FROM Party p WHERE p.name = :name"), @NamedQuery( name = "Party.searchByName", query = "SELECT p FROM Party p " + "WHERE LOWER(p.name) LIKE CONCAT(LOWER(:term), '%') " - + "ORDER BY p.name") - , + + "ORDER BY p.name"), @NamedQuery( name = "Party.findByRole", query = "SELECT p FROM Party p " @@ -99,7 +101,7 @@ public class Party implements Serializable { @GeneratedValue(strategy = GenerationType.AUTO) @XmlElement(name = "party-id", namespace = CORE_XML_NS) private long partyId; - + @Column(name = "UUID", unique = true, nullable = false) @XmlElement(name = "uuid", namespace = CORE_XML_NS) private String uuid; @@ -134,11 +136,11 @@ public class Party implements Serializable { protected void setPartyId(final long partyId) { this.partyId = partyId; } - + public String getUuid() { return uuid; } - + protected void setUuid(final String uuid) { this.uuid = uuid; } @@ -203,6 +205,29 @@ public class Party implements Serializable { return obj instanceof Party; } + public JsonObjectBuilder buildJson() { + final JsonArray array = roleMemberships + .stream() + .map(RoleMembership::buildJson) + .map(JsonObjectBuilder::build) + .collect(new JsonArrayCollector()); + + return Json + .createObjectBuilder() + .add("partyId", partyId) + .add("uuid", uuid) + .add("name", name) + .add( + "roleMemberships", + roleMemberships + .stream() + .map(RoleMembership::buildJson) + .map(JsonObjectBuilder::build) + .collect(new JsonArrayCollector()) + ); + + } + @Override public final String toString() { return toString(""); diff --git a/ccm-core/src/main/java/org/libreccm/security/RoleMembership.java b/ccm-core/src/main/java/org/libreccm/security/RoleMembership.java index 449169f80..acea95f50 100644 --- a/ccm-core/src/main/java/org/libreccm/security/RoleMembership.java +++ b/ccm-core/src/main/java/org/libreccm/security/RoleMembership.java @@ -34,6 +34,8 @@ import static org.libreccm.core.CoreConstants.DB_SCHEMA; import com.fasterxml.jackson.annotation.ObjectIdGenerators; import org.libreccm.imexport.Exportable; +import javax.json.Json; +import javax.json.JsonObjectBuilder; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; @@ -55,8 +57,7 @@ import javax.persistence.Table; @Table(name = "ROLE_MEMBERSHIPS", schema = DB_SCHEMA) @NamedQueries({ @NamedQuery(name = "RoleMembership.findByUuid", - query = "SELECT m FROM RoleMembership m WHERE m.uuid = :uuid") - , + query = "SELECT m FROM RoleMembership m WHERE m.uuid = :uuid"), @NamedQuery(name = "RoleMembership.findByRoleAndMember", query = "SELECT m FROM RoleMembership m " + "WHERE m.member = :member AND m.role = :role") @@ -160,6 +161,29 @@ public class RoleMembership implements Serializable, Exportable { return obj instanceof RoleMembership; } + public JsonObjectBuilder buildJson() { + return Json + .createObjectBuilder() + .add("membershipId", membershipId) + .add("uuid", uuid) + .add( + "role", + Json + .createObjectBuilder() + .add("roleId", role.getRoleId()) + .add("uuid", role.getUuid()) + .add("name", role.getName()) + ) + .add( + "member", + Json + .createObjectBuilder() + .add("partyId", member.getPartyId()) + .add("uuid", member.getUuid()) + .add("name", member.getName()) + ); + } + @Override public String toString() { return String.format("%s{ " diff --git a/ccm-core/src/main/java/org/libreccm/security/User.java b/ccm-core/src/main/java/org/libreccm/security/User.java index e34c1a932..ce71aa92c 100644 --- a/ccm-core/src/main/java/org/libreccm/security/User.java +++ b/ccm-core/src/main/java/org/libreccm/security/User.java @@ -30,6 +30,7 @@ import java.io.Serializable; import static org.libreccm.core.CoreConstants.CORE_XML_NS; import static org.libreccm.core.CoreConstants.DB_SCHEMA; +import org.libreccm.core.api.JsonArrayCollector; import org.libreccm.imexport.Exportable; import java.util.ArrayList; @@ -39,6 +40,9 @@ import java.util.List; import java.util.Objects; import java.util.Set; +import javax.json.Json; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObjectBuilder; import javax.persistence.AssociationOverride; import javax.persistence.CollectionTable; import javax.persistence.Column; @@ -73,30 +77,25 @@ import javax.xml.bind.annotation.XmlTransient; @Table(name = "USERS", schema = DB_SCHEMA) @NamedQueries({ @NamedQuery(name = "User.findByUuid", - query = "SELECT u FROM User u WHERE u.uuid = :uuid") - , + query = "SELECT u FROM User u WHERE u.uuid = :uuid"), @NamedQuery(name = "User.findByName", query = "SELECT u FROM User u WHERE u.name = :name " + "ORDER BY u.name, " + " u.familyName, " + " u.givenName, " - + " u.primaryEmailAddress.address") - , + + " u.primaryEmailAddress.address"), @NamedQuery(name = "User.countByName", - query = "SELECT COUNT(u) FROM User u WHERE u.name = :name") - , + query = "SELECT COUNT(u) FROM User u WHERE u.name = :name"), @NamedQuery(name = "User.findByEmailAddress", query = "SELECT u FROM User u WHERE " + "u.primaryEmailAddress.address = :emailAddress " + "ORDER BY u.name, " + " u.familyName, " + " u.givenName, " - + " u.primaryEmailAddress.address") - , + + " u.primaryEmailAddress.address"), @NamedQuery(name = "User.countByPrimaryEmailAddress", query = "SELECT COUNT(u) FROM User u " - + "WHERE u.primaryEmailAddress.address = :emailAddress") - , + + "WHERE u.primaryEmailAddress.address = :emailAddress"), @NamedQuery( name = "User.filterByNameAndEmail", query = "SELECT u FROM User u WHERE " @@ -107,15 +106,13 @@ import javax.xml.bind.annotation.XmlTransient; + "ORDER BY u.name," + "u.familyName, " + "u.givenName, " - + "u.primaryEmailAddress.address") - , + + "u.primaryEmailAddress.address"), @NamedQuery( name = "User.findAllOrderedByUsername", query = "SELECT u FROM User u ORDER BY u.name, " + " u.familyName, " + " u.givenName, " - + " u.primaryEmailAddress.address") - , + + " u.primaryEmailAddress.address"), @NamedQuery(name = "User.findByGroup", query = "SELECT u FROM User u " + "JOIN u.groupMemberships m " @@ -126,8 +123,7 @@ import javax.xml.bind.annotation.XmlTransient; name = "User.withGroupAndRoleMemberships", attributeNodes = { @NamedAttributeNode( - value = "groupMemberships") - , + value = "groupMemberships"), @NamedAttributeNode( value = "roleMemberships", subgraph = "role")}, @@ -137,8 +133,7 @@ import javax.xml.bind.annotation.XmlTransient; attributeNodes = { @NamedAttributeNode(value = "role", subgraph = "permissions") - }) - , + }), @NamedSubgraph( name = "permissions", attributeNodes = { @@ -366,6 +361,40 @@ public class User extends Party implements Serializable, Exportable { return obj instanceof User; } + @Override + public JsonObjectBuilder buildJson() { + final JsonArrayBuilder emailAddressesArrayBuilder = Json.createArrayBuilder(); + + emailAddresses + .stream() + .map(EmailAddress::buildJson) + .forEach(emailAddressesArrayBuilder::add); + + return super + .buildJson() + .add("givenName", givenName) + .add("familyName", familyName) + .add("primaryEmailAddress", primaryEmailAddress.buildJson()) + .add( + "emailAddresses", + emailAddresses + .stream() + .map(EmailAddress::buildJson) + .map(JsonObjectBuilder::build) + .collect(new JsonArrayCollector()) + ) + .add("banned", banned) + .add("passwordResetRequired", passwordResetRequired) + .add( + "groupMemberships", + groupMemberships + .stream() + .map(GroupMembership::buildJson) + .map(JsonObjectBuilder::build) + .collect(new JsonArrayCollector()) + ); + } + @Override public String toString(final String data) { return super.toString(String.format( diff --git a/ccm-core/src/main/java/org/libreccm/security/UsersApi.java b/ccm-core/src/main/java/org/libreccm/security/UsersApi.java new file mode 100644 index 000000000..b14e840a0 --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/security/UsersApi.java @@ -0,0 +1,131 @@ +/* + * 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.apache.shiro.authz.annotation.RequiresAuthentication; +import org.libreccm.core.CoreConstants; +import org.libreccm.core.api.ApiConstants; +import org.libreccm.core.api.ExtractedIdentifier; +import org.libreccm.core.api.IdentifierExtractor; +import org.libreccm.core.api.JsonArrayCollector; + +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.DefaultValue; +import javax.ws.rs.GET; +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("/users") +public class UsersApi { + + @Inject + private IdentifierExtractor identifierExtractor; + + @Inject + private UserRepository userRepository; + + @GET + @Path("/") + @Produces(MediaType.APPLICATION_JSON) + @AuthorizationRequired + @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) + @Transactional(Transactional.TxType.REQUIRED) + public JsonArray getUsers( + @QueryParam("limit") @DefaultValue("20") final int limit, + @QueryParam("offset") @DefaultValue("0") final int offset + ) { + return userRepository + .findAll(limit, offset) + .stream() + .map(User::buildJson) + .map(JsonObjectBuilder::build) + .collect(new JsonArrayCollector()); + } + + @GET + @Path("/{userIdentifier}") + @Produces(MediaType.APPLICATION_JSON) + @AuthorizationRequired + @RequiresPrivilege(CoreConstants.PRIVILEGE_ADMIN) + @Transactional(Transactional.TxType.REQUIRED) + public JsonObject getUser( + final @PathParam("userIdentifier") 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) + ) + .buildJson() + .build(); + case UUID: + return userRepository + .findByUuid(identifier.getIdentifier()) + .orElseThrow( + () -> new WebApplicationException( + String.format( + "No user with ID %s found.", + identifier.getIdentifier() + ), + Response.Status.NOT_FOUND) + ) + .buildJson() + .build(); + default: + return userRepository + .findByName(identifier.getIdentifier()) + .orElseThrow( + () -> new WebApplicationException( + String.format( + "No user with ID %s found.", + identifier.getIdentifier() + ), + Response.Status.NOT_FOUND) + ) + .buildJson() + .build(); + } + } + +}