diff --git a/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/CertificateService.java b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/CertificateService.java new file mode 100644 index 00000000..74aeee29 --- /dev/null +++ b/HIRS_AttestationCA/src/main/java/hirs/attestationca/persist/service/CertificateService.java @@ -0,0 +1,76 @@ +package hirs.attestationca.persist.service; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.TypedQuery; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +@Service +public class CertificateService { + + private final EntityManager entityManager; + + @Autowired + public CertificateService(EntityManager entityManager) { + this.entityManager = entityManager; + } + + /** + * @param entityClass generic entity class + * @param searchableColumns list of the searchable column name + * @param searchText text that waas input in the search textbox + * @param archiveFlag + * @param pageable + * @param generic entity class + * @return + */ + public Page findBySearchableColumnsAndArchiveFlag(Class entityClass, + List searchableColumns, + String searchText, + Boolean archiveFlag, + Pageable pageable) { + CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); + CriteriaQuery query = criteriaBuilder.createQuery(entityClass); + Root certificate = query.from(entityClass); + + List predicates = new ArrayList<>(); + + // Dynamically add search conditions for each field that should be searchable + if (!StringUtils.isBlank(searchText)) { + // Dynamically loop through columns and create LIKE conditions for each one + for (String columnName : searchableColumns) { + Predicate predicate = + criteriaBuilder.like(criteriaBuilder.lower(certificate.get(columnName)), + "%" + searchText.toLowerCase() + "%"); + predicates.add(predicate); + } + } + + Predicate likeConditions = criteriaBuilder.or(predicates.toArray(new Predicate[0])); + + // Add archiveFlag condition if specified + query.where(criteriaBuilder.and(likeConditions, + criteriaBuilder.equal(certificate.get("archiveFlag"), archiveFlag))); + + // Apply pagination + TypedQuery typedQuery = entityManager.createQuery(query); + int totalRows = typedQuery.getResultList().size(); // Get the total count for pagination + typedQuery.setFirstResult((int) pageable.getOffset()); + typedQuery.setMaxResults(pageable.getPageSize()); + + // Wrap the result in a Page object to return pagination info + List resultList = typedQuery.getResultList(); + return new PageImpl<>(resultList, pageable, totalRows); + } +} diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/datatables/Column.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/datatables/Column.java index 7edeae31..95f303a2 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/datatables/Column.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/datatables/Column.java @@ -22,18 +22,20 @@ public class Column { /** * Column's data source. * - * @see http://datatables.net/reference/option/columns.data + * @see https://datatables.net/reference/option/columns.data */ @NotBlank private String data; + /** * Column's name. * * @see https://datatables.net/reference/option/columns.name */ + @NotBlank private String name; - + /** * Flag to indicate if this column is searchable (true) or not (false). * @@ -55,14 +57,4 @@ public class Column { */ @NotNull private Search search; - - /** - * Set the search value to apply to this column. - * - * @param searchValue if any, the search value to apply - */ - public void setSearchValue(final String searchValue) { - this.search.setValue(searchValue); - } - } diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/datatables/Search.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/datatables/Search.java index 6f961d48..ae8b6d38 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/datatables/Search.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/datatables/Search.java @@ -22,6 +22,7 @@ public class Search { */ @NotNull private String value = ""; + /** * true if the global filter should be treated as a regular expression for advanced searching, * false otherwise. Note that normally server-side processing scripts will not perform regular diff --git a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificatePageController.java b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificatePageController.java index 8b4dd1f4..5787a896 100644 --- a/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificatePageController.java +++ b/HIRS_AttestationCAPortal/src/main/java/hirs/attestationca/portal/page/controllers/CertificatePageController.java @@ -19,6 +19,7 @@ import hirs.attestationca.persist.entity.userdefined.certificate.IssuedAttestati import hirs.attestationca.persist.entity.userdefined.certificate.PlatformCredential; import hirs.attestationca.persist.entity.userdefined.certificate.attributes.ComponentIdentifier; import hirs.attestationca.persist.entity.userdefined.certificate.attributes.V2.ComponentIdentifierV2; +import hirs.attestationca.persist.service.CertificateService; import hirs.attestationca.persist.util.CredentialHelper; import hirs.attestationca.portal.datatables.Column; import hirs.attestationca.portal.datatables.DataTableInput; @@ -28,10 +29,7 @@ import hirs.attestationca.portal.page.PageController; import hirs.attestationca.portal.page.PageMessages; import hirs.attestationca.portal.page.params.NoPageParams; import hirs.attestationca.portal.page.utils.CertificateStringMapBuilder; -import jakarta.persistence.EntityManager; -import jakarta.persistence.criteria.Predicate; import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.constraints.NotNull; import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.StringUtils; import org.bouncycastle.util.encoders.DecoderException; @@ -39,7 +37,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; -import org.springframework.data.jpa.domain.Specification; import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; @@ -62,7 +59,6 @@ import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; -import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; @@ -70,6 +66,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -98,8 +95,7 @@ public class CertificatePageController extends PageController { private final IssuedCertificateRepository issuedCertificateRepository; private final CACredentialRepository caCredentialRepository; private final IDevIDCertificateRepository iDevIDCertificateRepository; - @Autowired(required = false) - private EntityManager entityManager; + private final CertificateService certificateService; private CertificateAuthorityCredential certificateAuthorityCredential; /** @@ -122,6 +118,7 @@ public class CertificatePageController extends PageController { final IssuedCertificateRepository issuedCertificateRepository, final CACredentialRepository caCredentialRepository, final IDevIDCertificateRepository iDevIDCertificateRepository, + final CertificateService certificateService, final X509Certificate acaCertificate) { super(Page.TRUST_CHAIN); this.certificateRepository = certificateRepository; @@ -131,6 +128,7 @@ public class CertificatePageController extends PageController { this.issuedCertificateRepository = issuedCertificateRepository; this.caCredentialRepository = caCredentialRepository; this.iDevIDCertificateRepository = iDevIDCertificateRepository; + this.certificateService = certificateService; try { certificateAuthorityCredential @@ -237,30 +235,16 @@ public class CertificatePageController extends PageController { } /** - * @return + * Helper method that returns a list of column names that are searchable. + * + * @return searchable column names */ - private String buildSearchQuery(@NotNull String searchTerm, List columns) { + private List findSearchableColumnsNames(List columns) { - StringBuilder searchQuery = new StringBuilder(); - - if (!StringUtils.isBlank(searchTerm)) { - // Iterate over columns and add search conditions for the ones that are searchable - boolean firstCondition = true; - - for (Column column : columns) { - if (column.isSearchable()) { - if (!firstCondition) { - searchQuery.append(" OR "); - } - - searchQuery.append(column.getName()).append(" LIKE '%").append(searchTerm).append("%'"); - - firstCondition = false; - } - } - } - - return searchQuery.toString(); + // grab all the columns that are searchable, then grab all of those columns names and + // create a list of those string names + return columns.stream().filter(Column::isSearchable).map(Column::getName) + .collect(Collectors.toList()); } /** @@ -282,12 +266,14 @@ public class CertificatePageController extends PageController { // attempt to get the column property based on the order index. String orderColumnName = input.getOrderColumnName(); + log.debug("Ordering on column: {}", orderColumnName); - //String searchQuery = buildSearchQuery(input.getSearch().getValue(), input.getColumns()); + String searchText = input.getSearch().getValue(); + List searchableColumns = findSearchableColumnsNames(input.getColumns()); int currentPage = input.getStart() / input.getLength(); - Pageable paging = PageRequest.of(currentPage, input.getLength(), Sort.by(orderColumnName)); + Pageable pageable = PageRequest.of(currentPage, input.getLength(), Sort.by(orderColumnName)); // special parsing for platform credential // Add the EndorsementCredential for each PlatformCredential based on the @@ -296,30 +282,18 @@ public class CertificatePageController extends PageController { case PLATFORMCREDENTIAL -> { FilteredRecordsList records = new FilteredRecordsList<>(); - org.springframework.data.domain.Page pagedResult = null; + org.springframework.data.domain.Page pagedResult; - if (StringUtils.isBlank(input.getSearch().getValue())) { + if (StringUtils.isBlank(searchText)) { pagedResult = - this.platformCertificateRepository.findByArchiveFlag(false, paging); + this.platformCertificateRepository.findByArchiveFlag(false, pageable); } else { - - Specification spec = (root, query, criteriaBuilder) -> { - List predicates = new ArrayList<>(); - - for (Column column : input.getColumns()) { - if (column.isSearchable()) { - predicates.add(criteriaBuilder.like( - criteriaBuilder.lower(root.get(column.getName())), - "%" + column.getSearch().getValue().toLowerCase() + "%" - )); - } - } - - // Combine predicates into a single query (AND all conditions) - return criteriaBuilder.and(predicates.toArray(new Predicate[0])); - }; - - pagedResult = platformCertificateRepository.findAll(spec, paging); + pagedResult = + this.certificateService.findBySearchableColumnsAndArchiveFlag( + PlatformCredential.class, + searchableColumns, + searchText, + false, pageable); } if (pagedResult.hasContent()) { @@ -353,8 +327,19 @@ public class CertificatePageController extends PageController { } case ENDORSEMENTCREDENTIAL -> { FilteredRecordsList records = new FilteredRecordsList<>(); - org.springframework.data.domain.Page pagedResult = - this.endorsementCredentialRepository.findByArchiveFlag(false, paging); + + org.springframework.data.domain.Page pagedResult; + + if (StringUtils.isBlank(searchText)) { + pagedResult = this.endorsementCredentialRepository.findByArchiveFlag(false, pageable); + } else { + pagedResult = + this.certificateService.findBySearchableColumnsAndArchiveFlag( + EndorsementCredential.class, + searchableColumns, + searchText, + false, pageable); + } if (pagedResult.hasContent()) { records.addAll(pagedResult.getContent()); @@ -370,8 +355,21 @@ public class CertificatePageController extends PageController { } case TRUSTCHAIN -> { FilteredRecordsList records = new FilteredRecordsList<>(); - org.springframework.data.domain.Page pagedResult = - this.caCredentialRepository.findByArchiveFlag(false, paging); + + org.springframework.data.domain.Page pagedResult; + + if (StringUtils.isBlank(searchText)) { + pagedResult = + this.caCredentialRepository.findByArchiveFlag(false, pageable); + } else { + pagedResult = + this.certificateService.findBySearchableColumnsAndArchiveFlag( + CertificateAuthorityCredential.class, + searchableColumns, + searchText, + false, pageable); + } + if (pagedResult.hasContent()) { records.addAll(pagedResult.getContent()); @@ -387,8 +385,19 @@ public class CertificatePageController extends PageController { } case ISSUEDCERTIFICATES -> { FilteredRecordsList records = new FilteredRecordsList<>(); - org.springframework.data.domain.Page pagedResult = - this.issuedCertificateRepository.findByArchiveFlag(false, paging); + org.springframework.data.domain.Page pagedResult; + + if (StringUtils.isBlank(searchText)) { + pagedResult = + this.issuedCertificateRepository.findByArchiveFlag(false, pageable); + } else { + pagedResult = + this.certificateService.findBySearchableColumnsAndArchiveFlag( + IssuedAttestationCertificate.class, + searchableColumns, + searchText, + false, pageable); + } if (pagedResult.hasContent()) { records.addAll(pagedResult.getContent()); @@ -403,9 +412,20 @@ public class CertificatePageController extends PageController { return new DataTableResponse<>(records, input); } case IDEVIDCERTIFICATE -> { - FilteredRecordsList records = new FilteredRecordsList(); - org.springframework.data.domain.Page pagedResult = - this.iDevIDCertificateRepository.findByArchiveFlag(false, paging); + FilteredRecordsList records = new FilteredRecordsList<>(); + org.springframework.data.domain.Page pagedResult; + + if (StringUtils.isBlank(searchText)) { + pagedResult = + this.iDevIDCertificateRepository.findByArchiveFlag(false, pageable); + } else { + pagedResult = + this.certificateService.findBySearchableColumnsAndArchiveFlag( + IDevIDCertificate.class, + searchableColumns, + searchText, + false, pageable); + } if (pagedResult.hasContent()) { records.addAll(pagedResult.getContent()); diff --git a/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/endorsement-key-credentials.jsp b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/endorsement-key-credentials.jsp index 427aab41..fa6c55f0 100644 --- a/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/endorsement-key-credentials.jsp +++ b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/endorsement-key-credentials.jsp @@ -7,94 +7,110 @@ <%@taglib prefix="my" tagdir="/WEB-INF/tags"%> <%-- CONTENT --%> + + + + + Endorsement Key Credentials - - - - Endorsement Key Credentials + +
+ + Import Endorsement Key Credentials + + + + + + + +
+
+
+ + + + + + + + + + + + + + +
DeviceIssuerTypeManufacturerModelVersionValid (begin)Valid (end)Options
+
+ -
-
\ No newline at end of file + //Set data tables + setDataTables("#endorsementKeyTable", url, columns); + }); + + + diff --git a/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/issued-certificates.jsp b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/issued-certificates.jsp index f0c8642b..5f5246ff 100644 --- a/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/issued-certificates.jsp +++ b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/issued-certificates.jsp @@ -7,131 +7,153 @@ <%@taglib prefix="my" tagdir="/WEB-INF/tags"%> <%-- CONTENT --%> + + + + + Issued Certificates + +
+ Issued Credentials + + + +
+
+
+ + + + + + + + + + + + + + + + +
HostnameTypeIssuerValid (begin)Valid (end)CredentialsOptions
EndorsementPlatform
+
+ - - Issued Certificates - -
- Issued Credentials - - - -
-
-
- - - - - - - - - - - - - - - - -
HostnameTypeIssuerValid (begin)Valid (end)CredentialsOptions
EndorsementPlatform
-
- -
+ return html; + }, + }, + ]; + //Set data tables + setDataTables("#issuedTable", url, columns); + }); + +
diff --git a/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/trust-chain.jsp b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/trust-chain.jsp index 4ee0c348..bb3a701f 100644 --- a/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/trust-chain.jsp +++ b/HIRS_AttestationCAPortal/src/main/webapp/WEB-INF/jsp/trust-chain.jsp @@ -8,148 +8,181 @@ <%-- CONTENT --%> - - - - Trust Chain Management + + + + Trust Chain Management - - - HIRS Attestation CA Certificate - - -
-
-
Issuer
-
- - - - - ${acaCertData.issuer} - - - - ${acaCertData.issuer} - - -
-
- -
-
Subject
-
${acaCertData.subject}
-
-
-
-
Serial Number
-
${acaCertData.serialNumber}
-
-
-
Validity
-
-
Not Before: ${acaCertData.beginValidity}
-
Not After: ${acaCertData.endValidity}
-
-
-
-
Signature
-
-
- -
-
Public Key
-
-
-
-
-
-
- - - - -
- - Trust Chain CA Certificates - - - - - + + HIRS Attestation CA Certificate + +
+ -
-
- - - - - - - - - - -
IssuerSubjectValid (begin)Valid (end)Options
+ +
+
+ Subject +
+
${acaCertData.subject}
+
+
+
+
+ Serial Number +
+
+ ${acaCertData.serialNumber} +
- - + + //Change publick key byte to hex + let publicKey = ${acaCertData.encodedPublicKey}; + $("#encodedPublicKey").html(byteToHexString(publicKey)); + - \ No newline at end of file + let columns = [ + { + name: "issuer", + data: "issuer", + orderable: true, + searchable: true, + }, + { + name: 'subject', + data: 'subject' + }, + { + name: 'beginValidity', + data: 'beginValidity', + searchable:false, + render: function (data, type, full, meta) { + return formatCertificateDate(full.beginValidity); + } + }, + { + name: 'endValidity', + data: 'endValidity', + searchable:false, + render: function (data, type, full, meta) { + return formatCertificateDate(full.endValidity); + } + }, + { + data: 'id', + orderable: false, + searchable:false, + render: function(data, type, full, meta) { + // Set up a delete icon with link to handleDeleteRequest(). + // sets up a hidden input field containing the ID which is + // used as a parameter to the REST POST call to delete + let html = ''; + html += certificateDetailsLink('certificateauthority', full.id, true); + html += certificateDownloadLink(full.id, pagePath); + html += certificateDeleteLink(full.id, pagePath); + return html; + } + } + ]; + //Set data tables + setDataTables("#trustChainTable", url, columns); + }); + + +