Merge branch 'master' of git.libreccm.org:LibreCCM/libreccm

Former-commit-id: 5476f3bcc2
pull/2/head
Jens Pelzetter 2020-05-11 20:38:31 +02:00
commit ff9031861f
18 changed files with 745 additions and 36 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
nb-configuration.xml
node node
node_modules node_modules
runtime runtime

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
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");
}
}
}

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public class DefaultResponseHeaders implements WriterInterceptor {
@Override
public void aroundWriteTo(WriterInterceptorContext context)
throws IOException, WebApplicationException {
context
.getHeaders()
.add("Access-Control-Allow-Origin", "*");
context.proceed();
}
}

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
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()
);
}
}
}

View File

@ -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;

View File

@ -135,12 +135,13 @@ public abstract class AbstractEntityRepository<K, E> implements Serializable {
/** /**
* Get the primary key/id of a entity. * Get the primary key/id of a entity.
* *
* @param entity The entity * @param entity The entity
*
* @return The ID of the provided {@code entity}. * @return The ID of the provided {@code entity}.
*/ */
public abstract K getIdOfEntity(E entity); public abstract K getIdOfEntity(E entity);
/** /**
* Finds an entity by it ID. * Finds an entity by it ID.
* *
@ -223,15 +224,15 @@ public abstract class AbstractEntityRepository<K, E> implements Serializable {
return Optional.empty(); return Optional.empty();
} }
} }
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public E reload(final E entity, final String... fetchJoins) { public E reload(final E entity, final String... fetchJoins) {
return findById(getIdOfEntity(entity), fetchJoins) return findById(getIdOfEntity(entity), fetchJoins)
.orElseThrow(() -> new IllegalArgumentException(String .orElseThrow(() -> new IllegalArgumentException(String
.format("No Entity of type \"%s\" with ID %s in the database.", .format("No Entity of type \"%s\" with ID %s in the database.",
getEntityClass().getName(), getEntityClass().getName(),
Objects.toString(getIdOfEntity(entity))))); Objects.toString(getIdOfEntity(entity)))));
} }
/** /**
@ -248,6 +249,10 @@ public abstract class AbstractEntityRepository<K, E> implements Serializable {
return executeCriteriaQuery(createCriteriaQuery()); return executeCriteriaQuery(createCriteriaQuery());
} }
public List<E> findAll(final int limit, final int offset) {
return executeCriteriaQuery(createCriteriaQuery(), limit, offset);
}
@Transactional(Transactional.TxType.REQUIRED) @Transactional(Transactional.TxType.REQUIRED)
public List<E> findAll(final String entityGraphName) { public List<E> findAll(final String entityGraphName) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -282,6 +287,18 @@ public abstract class AbstractEntityRepository<K, E> implements Serializable {
return query.getResultList(); return query.getResultList();
} }
public List<E> executeCriteriaQuery(
final CriteriaQuery<E> criteriaQuery,
final int limit,
final int offset
) {
return entityManager
.createQuery(criteriaQuery)
.setFirstResult(offset)
.setMaxResults(limit)
.getResultList();
}
public List<E> executeCriteriaQuery(final CriteriaQuery<E> criteriaQuery, public List<E> executeCriteriaQuery(final CriteriaQuery<E> criteriaQuery,
final String graphName) { final String graphName) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")

View File

@ -25,11 +25,15 @@ import javax.persistence.Column;
import javax.persistence.Embeddable; import javax.persistence.Embeddable;
import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlRootElement;
import java.io.Serializable; import java.io.Serializable;
import java.util.Objects; import java.util.Objects;
import static org.libreccm.core.CoreConstants.CORE_XML_NS; import static org.libreccm.core.CoreConstants.CORE_XML_NS;
import javax.json.Json;
import javax.json.JsonObjectBuilder;
/** /**
* An embeddable entity for storing email addresses. * An embeddable entity for storing email addresses.
* *
@ -122,6 +126,14 @@ public class EmailAddress implements Serializable {
return obj instanceof EmailAddress; return obj instanceof EmailAddress;
} }
public JsonObjectBuilder buildJson() {
return Json
.createObjectBuilder()
.add("address", address)
.add("bouncing", bouncing)
.add("verified", verified);
}
@Override @Override
public String toString() { public String toString() {
return String.format("%s{ " return String.format("%s{ "

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public final class ApiConstants {
private ApiConstants() {
// Nothing
}
public static final String IDENTIFIER_PREFIX_ID = "ID-";
public static final String IDENTIFIER_PREFIX_UUID = "UUID-";
}

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@ApplicationPath("/api/ccm-core")
public class CcmCoreApi extends Application {
@Override
public Set<Class<?>> getClasses() {
final Set<Class<?>> classes = new HashSet<>();
classes.add(CorsFilter.class);
classes.add(DefaultResponseHeaders.class);
classes.add(PreflightRequestFilter.class);
classes.add(UsersApi.class);
return classes;
}
}

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
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;
}
}

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@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
);
}
}
}

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public enum IdentifierType {
ID,
UUID,
PROPERTY
}

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public class JsonArrayCollector implements Collector<JsonValue, JsonArrayBuilder, JsonArray>{
@Override
public Supplier<JsonArrayBuilder> supplier() {
return Json::createArrayBuilder;
}
@Override
public BiConsumer<JsonArrayBuilder, JsonValue> accumulator() {
return JsonArrayBuilder::add;
}
@Override
public BinaryOperator<JsonArrayBuilder> combiner() {
return (left, right) -> left.add(right);
}
@Override
public Function<JsonArrayBuilder, JsonArray> finisher() {
return JsonArrayBuilder::build;
}
@Override
public Set<Characteristics> characteristics() {
return Collections.emptySet();
}
}

View File

@ -34,6 +34,9 @@ import static org.libreccm.core.CoreConstants.DB_SCHEMA;
import com.fasterxml.jackson.annotation.ObjectIdGenerators; import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import org.libreccm.imexport.Exportable; import org.libreccm.imexport.Exportable;
import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.GeneratedValue; import javax.persistence.GeneratedValue;
@ -55,8 +58,7 @@ import javax.persistence.Table;
@Table(name = "GROUP_MEMBERSHIPS", schema = DB_SCHEMA) @Table(name = "GROUP_MEMBERSHIPS", schema = DB_SCHEMA)
@NamedQueries({ @NamedQueries({
@NamedQuery(name = "GroupMembership.findByUuid", @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", @NamedQuery(name = "GroupMembership.findByGroupAndUser",
query = "SELECT m FROM GroupMembership m " query = "SELECT m FROM GroupMembership m "
+ "WHERE m.member = :member AND m.group = :group")}) + "WHERE m.member = :member AND m.group = :group")})
@ -158,6 +160,30 @@ public class GroupMembership implements Serializable, Exportable {
return obj instanceof GroupMembership; 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 @Override
public String toString() { public String toString() {
return String.format("%s{ " return String.format("%s{ "

View File

@ -23,6 +23,7 @@ import static org.libreccm.core.CoreConstants.*;
import com.fasterxml.jackson.annotation.JsonIdentityInfo; import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.ObjectIdGenerators; import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import org.libreccm.core.api.JsonArrayCollector;
import javax.persistence.FetchType; import javax.persistence.FetchType;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
@ -35,6 +36,9 @@ import java.util.HashSet;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonObjectBuilder;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.GeneratedValue; import javax.persistence.GeneratedValue;
@ -67,14 +71,12 @@ import javax.persistence.Table;
), ),
@NamedQuery( @NamedQuery(
name = "Party.findByName", name = "Party.findByName",
query = "SELECT p FROM Party p WHERE p.name = :name") query = "SELECT p FROM Party p WHERE p.name = :name"),
,
@NamedQuery( @NamedQuery(
name = "Party.searchByName", name = "Party.searchByName",
query = "SELECT p FROM Party p " query = "SELECT p FROM Party p "
+ "WHERE LOWER(p.name) LIKE CONCAT(LOWER(:term), '%') " + "WHERE LOWER(p.name) LIKE CONCAT(LOWER(:term), '%') "
+ "ORDER BY p.name") + "ORDER BY p.name"),
,
@NamedQuery( @NamedQuery(
name = "Party.findByRole", name = "Party.findByRole",
query = "SELECT p FROM Party p " query = "SELECT p FROM Party p "
@ -99,7 +101,7 @@ public class Party implements Serializable {
@GeneratedValue(strategy = GenerationType.AUTO) @GeneratedValue(strategy = GenerationType.AUTO)
@XmlElement(name = "party-id", namespace = CORE_XML_NS) @XmlElement(name = "party-id", namespace = CORE_XML_NS)
private long partyId; private long partyId;
@Column(name = "UUID", unique = true, nullable = false) @Column(name = "UUID", unique = true, nullable = false)
@XmlElement(name = "uuid", namespace = CORE_XML_NS) @XmlElement(name = "uuid", namespace = CORE_XML_NS)
private String uuid; private String uuid;
@ -134,11 +136,11 @@ public class Party implements Serializable {
protected void setPartyId(final long partyId) { protected void setPartyId(final long partyId) {
this.partyId = partyId; this.partyId = partyId;
} }
public String getUuid() { public String getUuid() {
return uuid; return uuid;
} }
protected void setUuid(final String uuid) { protected void setUuid(final String uuid) {
this.uuid = uuid; this.uuid = uuid;
} }
@ -203,6 +205,29 @@ public class Party implements Serializable {
return obj instanceof Party; 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 @Override
public final String toString() { public final String toString() {
return toString(""); return toString("");

View File

@ -34,6 +34,8 @@ import static org.libreccm.core.CoreConstants.DB_SCHEMA;
import com.fasterxml.jackson.annotation.ObjectIdGenerators; import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import org.libreccm.imexport.Exportable; import org.libreccm.imexport.Exportable;
import javax.json.Json;
import javax.json.JsonObjectBuilder;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.GeneratedValue; import javax.persistence.GeneratedValue;
@ -55,8 +57,7 @@ import javax.persistence.Table;
@Table(name = "ROLE_MEMBERSHIPS", schema = DB_SCHEMA) @Table(name = "ROLE_MEMBERSHIPS", schema = DB_SCHEMA)
@NamedQueries({ @NamedQueries({
@NamedQuery(name = "RoleMembership.findByUuid", @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", @NamedQuery(name = "RoleMembership.findByRoleAndMember",
query = "SELECT m FROM RoleMembership m " query = "SELECT m FROM RoleMembership m "
+ "WHERE m.member = :member AND m.role = :role") + "WHERE m.member = :member AND m.role = :role")
@ -160,6 +161,29 @@ public class RoleMembership implements Serializable, Exportable {
return obj instanceof RoleMembership; 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 @Override
public String toString() { public String toString() {
return String.format("%s{ " return String.format("%s{ "

View File

@ -30,6 +30,7 @@ import java.io.Serializable;
import static org.libreccm.core.CoreConstants.CORE_XML_NS; import static org.libreccm.core.CoreConstants.CORE_XML_NS;
import static org.libreccm.core.CoreConstants.DB_SCHEMA; import static org.libreccm.core.CoreConstants.DB_SCHEMA;
import org.libreccm.core.api.JsonArrayCollector;
import org.libreccm.imexport.Exportable; import org.libreccm.imexport.Exportable;
import java.util.ArrayList; import java.util.ArrayList;
@ -39,6 +40,9 @@ import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObjectBuilder;
import javax.persistence.AssociationOverride; import javax.persistence.AssociationOverride;
import javax.persistence.CollectionTable; import javax.persistence.CollectionTable;
import javax.persistence.Column; import javax.persistence.Column;
@ -73,30 +77,25 @@ import javax.xml.bind.annotation.XmlTransient;
@Table(name = "USERS", schema = DB_SCHEMA) @Table(name = "USERS", schema = DB_SCHEMA)
@NamedQueries({ @NamedQueries({
@NamedQuery(name = "User.findByUuid", @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", @NamedQuery(name = "User.findByName",
query = "SELECT u FROM User u WHERE u.name = :name " query = "SELECT u FROM User u WHERE u.name = :name "
+ "ORDER BY u.name, " + "ORDER BY u.name, "
+ " u.familyName, " + " u.familyName, "
+ " u.givenName, " + " u.givenName, "
+ " u.primaryEmailAddress.address") + " u.primaryEmailAddress.address"),
,
@NamedQuery(name = "User.countByName", @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", @NamedQuery(name = "User.findByEmailAddress",
query = "SELECT u FROM User u WHERE " query = "SELECT u FROM User u WHERE "
+ "u.primaryEmailAddress.address = :emailAddress " + "u.primaryEmailAddress.address = :emailAddress "
+ "ORDER BY u.name, " + "ORDER BY u.name, "
+ " u.familyName, " + " u.familyName, "
+ " u.givenName, " + " u.givenName, "
+ " u.primaryEmailAddress.address") + " u.primaryEmailAddress.address"),
,
@NamedQuery(name = "User.countByPrimaryEmailAddress", @NamedQuery(name = "User.countByPrimaryEmailAddress",
query = "SELECT COUNT(u) FROM User u " query = "SELECT COUNT(u) FROM User u "
+ "WHERE u.primaryEmailAddress.address = :emailAddress") + "WHERE u.primaryEmailAddress.address = :emailAddress"),
,
@NamedQuery( @NamedQuery(
name = "User.filterByNameAndEmail", name = "User.filterByNameAndEmail",
query = "SELECT u FROM User u WHERE " query = "SELECT u FROM User u WHERE "
@ -107,15 +106,13 @@ import javax.xml.bind.annotation.XmlTransient;
+ "ORDER BY u.name," + "ORDER BY u.name,"
+ "u.familyName, " + "u.familyName, "
+ "u.givenName, " + "u.givenName, "
+ "u.primaryEmailAddress.address") + "u.primaryEmailAddress.address"),
,
@NamedQuery( @NamedQuery(
name = "User.findAllOrderedByUsername", name = "User.findAllOrderedByUsername",
query = "SELECT u FROM User u ORDER BY u.name, " query = "SELECT u FROM User u ORDER BY u.name, "
+ " u.familyName, " + " u.familyName, "
+ " u.givenName, " + " u.givenName, "
+ " u.primaryEmailAddress.address") + " u.primaryEmailAddress.address"),
,
@NamedQuery(name = "User.findByGroup", @NamedQuery(name = "User.findByGroup",
query = "SELECT u FROM User u " query = "SELECT u FROM User u "
+ "JOIN u.groupMemberships m " + "JOIN u.groupMemberships m "
@ -126,8 +123,7 @@ import javax.xml.bind.annotation.XmlTransient;
name = "User.withGroupAndRoleMemberships", name = "User.withGroupAndRoleMemberships",
attributeNodes = { attributeNodes = {
@NamedAttributeNode( @NamedAttributeNode(
value = "groupMemberships") value = "groupMemberships"),
,
@NamedAttributeNode( @NamedAttributeNode(
value = "roleMemberships", value = "roleMemberships",
subgraph = "role")}, subgraph = "role")},
@ -137,8 +133,7 @@ import javax.xml.bind.annotation.XmlTransient;
attributeNodes = { attributeNodes = {
@NamedAttributeNode(value = "role", @NamedAttributeNode(value = "role",
subgraph = "permissions") subgraph = "permissions")
}) }),
,
@NamedSubgraph( @NamedSubgraph(
name = "permissions", name = "permissions",
attributeNodes = { attributeNodes = {
@ -366,6 +361,40 @@ public class User extends Party implements Serializable, Exportable {
return obj instanceof User; 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 @Override
public String toString(final String data) { public String toString(final String data) {
return super.toString(String.format( return super.toString(String.format(

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@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();
}
}
}