added oidc authentication

This commit is contained in:
2023-09-07 08:15:02 +08:00
parent b9851eecaf
commit 214243e465
11 changed files with 99 additions and 54 deletions

View File

@@ -9,4 +9,5 @@ module net.woggioni.jpacrepo.api {
exports net.woggioni.jpacrepo.api.model;
exports net.woggioni.jpacrepo.api.service;
exports net.woggioni.jpacrepo.api.wire;
exports net.woggioni.jpacrepo.api.security;
}

View File

@@ -0,0 +1,9 @@
package net.woggioni.jpacrepo.api.security;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Roles {
public static final String WRITER = "jpacrepo";
}

View File

@@ -1,5 +1,6 @@
package net.woggioni.jpacrepo.api.service;
import jakarta.annotation.security.PermitAll;
import jakarta.ejb.Local;
import net.woggioni.jpacrepo.api.model.CompressionFormat;
import net.woggioni.jpacrepo.api.model.PkgData;
@@ -7,6 +8,7 @@ import net.woggioni.jpacrepo.api.model.PkgData;
import java.util.List;
@Local
@PermitAll
public interface PacmanServiceLocal extends PacmanServiceRemote {
long countResults(String name, String version, String arch);
List<PkgData> searchPackage(String name,

View File

@@ -2,10 +2,13 @@ package net.woggioni.jpacrepo.api.service;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import jakarta.annotation.security.PermitAll;
import jakarta.annotation.security.RolesAllowed;
import jakarta.ejb.Remote;
import net.woggioni.jpacrepo.api.model.CompressionFormat;
import net.woggioni.jpacrepo.api.model.PkgData;
import net.woggioni.jpacrepo.api.model.PkgId;
import net.woggioni.jpacrepo.api.security.Roles;
import net.woggioni.jpacrepo.api.wire.PkgTuple;
import java.io.InputStream;
@@ -15,6 +18,7 @@ import java.util.NavigableMap;
import java.util.Set;
@Remote
@PermitAll
public interface PacmanServiceRemote {
void syncDB();
@@ -42,9 +46,11 @@ public interface PacmanServiceRemote {
@Nullable
Long getFileSize(String fileName);
@RolesAllowed(Roles.WRITER)
Set<String> missingFiles(Collection<String> fileNames);
NavigableMap<PkgId, PkgTuple> getPkgMap();
@RolesAllowed(Roles.WRITER)
boolean addPackage(String fileName, InputStream inputStream);
}

View File

@@ -70,6 +70,7 @@ import java.util.stream.Stream;
@Local({PacmanServiceLocal.class})
@Remote({PacmanServiceRemote.class})
public class PacmanServiceEJB implements PacmanServiceLocal {
@PersistenceContext
private EntityManager em;

View File

@@ -1,5 +1,6 @@
package net.woggioni.jpacrepo.service;
import jakarta.annotation.security.PermitAll;
import jakarta.ejb.ConcurrencyManagement;
import jakarta.ejb.ConcurrencyManagementType;
import jakarta.ejb.TransactionAttribute;
@@ -7,11 +8,8 @@ import jakarta.ejb.TransactionAttributeType;
import jakarta.ejb.TransactionManagement;
import jakarta.ejb.TransactionManagementType;
import jakarta.inject.Inject;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.NoResultException;
import jakarta.persistence.NonUniqueResultException;
import jakarta.persistence.TypedQuery;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.FormParam;
@@ -31,25 +29,22 @@ import jakarta.ws.rs.core.EntityTag;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Request;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.SecurityContext;
import jakarta.ws.rs.core.StreamingOutput;
import jakarta.ws.rs.core.UriInfo;
import lombok.SneakyThrows;
import net.woggioni.jpacrepo.api.model.CompressionFormat;
import net.woggioni.jpacrepo.api.model.PkgData;
import net.woggioni.jpacrepo.api.model.PkgId;
import net.woggioni.jpacrepo.api.security.Roles;
import net.woggioni.jpacrepo.api.service.PacmanServiceLocal;
import net.woggioni.jpacrepo.config.AppConfig;
import net.woggioni.jpacrepo.impl.model.CompressionFormatImpl;
import net.woggioni.jpacrepo.impl.model.PkgDataImpl;
import net.woggioni.jpacrepo.api.wire.PkgDataList;
import net.woggioni.jpacrepo.api.wire.PkgTuple;
import net.woggioni.jpacrepo.api.wire.StringList;
import net.woggioni.jpacrepo.version.PkgIdComparator;
import net.woggioni.jpacrepo.config.AppConfig;
import net.woggioni.jpacrepo.version.VersionComparator;
import net.woggioni.jwo.CollectionUtils;
import net.woggioni.jwo.Con;
import net.woggioni.jwo.JWO;
import net.woggioni.jwo.Tuple2;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.slf4j.Logger;
@@ -60,10 +55,9 @@ import java.net.URI;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
@@ -72,14 +66,15 @@ import java.util.Optional;
import java.util.TreeMap;
import java.util.stream.Collectors;
import static net.woggioni.jpacrepo.api.security.Roles.WRITER;
@Path("/pkg")
@PermitAll
@ConcurrencyManagement(ConcurrencyManagementType.BEAN)
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@TransactionManagement(TransactionManagementType.CONTAINER)
public class PacmanWebService {
@Inject
private EntityManagerFactory emf;
@Inject
private Logger log;
@@ -139,7 +134,7 @@ public class PacmanWebService {
cc.setNoCache(true);
NavigableMap<PkgId, PkgTuple> cachedMap = service.getPkgMap();
EntityTag etag = new EntityTag(Integer.toString(cachedMap.hashCode()), false);
EntityTag etag = new EntityTag(Integer.toString(cachedMap.hashCode()), true);
Response.ResponseBuilder builder = request.evaluatePreconditions(etag);
if (builder == null) {
@@ -238,8 +233,10 @@ public class PacmanWebService {
@POST
@Path("/doYouWantAny")
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response doYouWantAny(List<String> fileNames) {
if (fileNames.isEmpty()) {
public Response doYouWantAny(List<String> fileNames, @Context SecurityContext sctx) {
if(!sctx.isUserInRole(WRITER)) {
return Response.status(Response.Status.UNAUTHORIZED).build();
} else if (fileNames.isEmpty()) {
return Response.ok(fileNames).build();
}
else {
@@ -255,9 +252,11 @@ public class PacmanWebService {
public Response createPackage(
InputStream input,
@MatrixParam("filename") String filename,
@Context UriInfo uriInfo) {
EntityManager em = emf.createEntityManager();
if (filename == null) throw new BadRequestException();
@Context UriInfo uriInfo,
@Context SecurityContext sctx) {
if(!sctx.isUserInRole(Roles.WRITER)) {
return Response.status(Response.Status.UNAUTHORIZED).build();
} else if (filename == null) throw new BadRequestException();
Response result;
boolean added = service.addPackage(filename, input);
if(!added) {
@@ -334,4 +333,14 @@ public class PacmanWebService {
};
return Response.ok(stream).header("Content-Disposition", "attachment; filename=pkgs.tar").build();
}
@GET
@Path("whoAmI")
@SneakyThrows
public Response whoAmI(@Context SecurityContext securityContext) {
Map<String, String> result = new HashMap<>();
result.put("user", securityContext.getUserPrincipal().getName());
result.put("jpacrepo", Boolean.toString(securityContext.isUserInRole("jpacrepo")));
return Response.ok(result).build();
}
}

View File

@@ -1,18 +1,23 @@
package net.woggioni.jpacrepo.service;
import jakarta.annotation.security.DeclareRoles;
import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;
import net.woggioni.jpacrepo.api.security.Roles;
import net.woggioni.jpacrepo.service.exception.EJBAccessExceptionMapper;
import java.util.HashSet;
import java.util.Set;
@ApplicationPath("api")
@DeclareRoles({Roles.WRITER})
public class WxRsApplication extends Application {
@Override
public Set<Class<?>> getClasses() {
Set<Class<?>> result = new HashSet<>();
result.add(PacmanWebService.class);
result.add(EJBAccessExceptionMapper.class);
return result;
}
}

View File

@@ -0,0 +1,12 @@
package net.woggioni.jpacrepo.service.exception;
import jakarta.ejb.EJBAccessException;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
public class EJBAccessExceptionMapper implements ExceptionMapper<EJBAccessException> {
@Override
public Response toResponse(EJBAccessException exception) {
return Response.status(Response.Status.UNAUTHORIZED).build();
}
}

View File

@@ -1,35 +0,0 @@
package net.woggioni.jpacrepo.service.jpa;
import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.criteria.Selection;
import jakarta.persistence.metamodel.Metamodel;
public class QueryBuilder<T> {
private final EntityManager em;
private final CriteriaBuilder cb;
private final CriteriaQuery<T> criteriaQuery;
private final Metamodel metamodel;
public QueryBuilder(EntityManager em, Class<T> cls) {
this.em = em;
this.cb = em.getCriteriaBuilder();
this.criteriaQuery = cb.createQuery(cls);
this.metamodel = em.getMetamodel();
}
public QueryBuilder<T> select(Selection<? extends T> a) {
criteriaQuery.select(a);
return this;
}
public <U> Root<U> from(Class<U> cls) {
return criteriaQuery.from(metamodel.entity(cls));
}
public TypedQuery<T> build() {
return em.createQuery(criteriaQuery);
}
}

View File

@@ -0,0 +1,14 @@
{
"realm": "woggioni.net",
"auth-server-url": "https://woggioni.net/auth",
"public-client": false,
"ssl-required": "all",
"resource": "jpacrepo",
"credentials": {
"secret": "${env.JPACREPO_OIDC_CLIENT_SECRET}"
},
"verify-token-audience": true,
"confidential-port": 443,
"token-signature-algorithm": "ES512",
"use-resource-role-mappings": true
}

View File

@@ -0,0 +1,21 @@
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<login-config>
<auth-method>OIDC</auth-method>
</login-config>
<security-constraint>
<web-resource-collection>
<web-resource-name>login2</web-resource-name>
<url-pattern>/api/pkg/doYouWantAny</url-pattern>
<url-pattern>/api/pkg/upload</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>jpacrepo</role-name>
</auth-constraint>
</security-constraint>
<security-role>
<role-name>jpacrepo</role-name>
</security-role>
</web-app>