From 646c4b38c9be931cdd72c2fd554252b1c6a210ca Mon Sep 17 00:00:00 2001 From: Manoj Garai Date: Mon, 15 Sep 2025 17:08:29 +0100 Subject: [PATCH 1/9] Display list of user and raop --- .../ac/ngs/controllers/DisplayUserRaop.java | 107 ++++++++++++++++++ .../uk/ac/ngs/dao/JdbcCertificateDao.java | 51 +++++++++ src/main/webapp/WEB-INF/jspf/header.jspf | 1 + .../WEB-INF/views/raop/displayuserraop.jsp | 81 +++++++++++++ 4 files changed, 240 insertions(+) create mode 100644 src/main/java/uk/ac/ngs/controllers/DisplayUserRaop.java create mode 100644 src/main/webapp/WEB-INF/views/raop/displayuserraop.jsp diff --git a/src/main/java/uk/ac/ngs/controllers/DisplayUserRaop.java b/src/main/java/uk/ac/ngs/controllers/DisplayUserRaop.java new file mode 100644 index 0000000..3494e17 --- /dev/null +++ b/src/main/java/uk/ac/ngs/controllers/DisplayUserRaop.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2015 STFC + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package uk.ac.ngs.controllers; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.security.access.annotation.Secured; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import uk.ac.ngs.dao.JdbcCertificateDao; +import uk.ac.ngs.dao.JdbcCrrDao; +import uk.ac.ngs.dao.JdbcRaopListDao; +import uk.ac.ngs.dao.JdbcRequestDao; +import uk.ac.ngs.domain.CertificateRow; +import uk.ac.ngs.domain.CrrRow; +import uk.ac.ngs.domain.RaopListRow; +import uk.ac.ngs.domain.RequestRow; +import uk.ac.ngs.security.CaUser; +import uk.ac.ngs.security.SecurityContextService; +import uk.ac.ngs.service.CertUtil; + +import javax.inject.Inject; +import java.util.*; +import java.util.stream.Collectors; + +//import org.springframework.security.core.GrantedAuthority; + +/** + * @author David Meredith + */ +@Controller +@RequestMapping("/raop/displayuserraop") +@Secured("ROLE_RAOP") +public class DisplayUserRaop { + + private static final Log log = LogFactory.getLog(DisplayUserRaop.class); + private SecurityContextService securityContextService; + private JdbcRaopListDao jdbcRaopListDao; + private JdbcCertificateDao jdbcCertificateDao; + + public DisplayUserRaop(SecurityContextService securityContextService, JdbcRaopListDao jdbcRaopListDao, + JdbcCertificateDao jdbcCertificateDao) { + this.securityContextService = securityContextService; + this.jdbcRaopListDao = jdbcRaopListDao; + this.jdbcCertificateDao = jdbcCertificateDao; + } + + + @ModelAttribute + public void populateModel(Model model) { + log.debug("DisplayUserRaop populateModel"); + + // Get current user and their DN + CaUser caUser = securityContextService.getCaUserDetails(); + String currentUserDn = caUser.getCertificateRow().getDn(); + + // Extract DN attributes + String ou = CertUtil.extractDnAttribute(currentUserDn, CertUtil.DNAttributeType.OU); + String o = CertUtil.extractDnAttribute(currentUserDn, CertUtil.DNAttributeType.O); + String loc = CertUtil.extractDnAttribute(currentUserDn, CertUtil.DNAttributeType.L); + + // Build RA string and add to model + String ra = String.format("%s %s", ou, loc); + model.addAttribute("ra", ra); + log.debug("ra is:[" + ra + "]"); + + // Fetch active users and RA operators for the current RA + List userRaopRows = jdbcCertificateDao.findActiveUserAndRAOperatorBy(ou, o, loc); + + // Exclude current user from the list + List filteredRows = userRaopRows.stream() + .filter(row -> !currentUserDn.equals(row.getDn())) + .collect(Collectors.toList()); + + log.debug("Filtered User and RA Operator rows size: " + filteredRows.size()); + model.addAttribute("userRaopRows", filteredRows); + + // Add timestamp + model.addAttribute("lastPageRefreshDate", new Date()); +} + + + /** + * Select the raop/displayuserraop view to render. + * + * @return raop/displayuserraop + */ + @RequestMapping(method = RequestMethod.GET) + public String displayUserRaop(Locale locale, Model model) { + log.debug("Controller /raop/"); + return "raop/displayuserraop"; + } +} diff --git a/src/main/java/uk/ac/ngs/dao/JdbcCertificateDao.java b/src/main/java/uk/ac/ngs/dao/JdbcCertificateDao.java index 4c6f9a1..edb9bd7 100644 --- a/src/main/java/uk/ac/ngs/dao/JdbcCertificateDao.java +++ b/src/main/java/uk/ac/ngs/dao/JdbcCertificateDao.java @@ -285,6 +285,57 @@ public List findActiveRAsBy(String loc, String ou) { return this.jdbcTemplate.query(query.toString(), namedParameters, new CertificateRowMapper()); } + /** + * Find all rows with role 'RA Operator' or 'User' with a + * status of 'VALID' and a 'notafter' time that is in the future with the + * specified o (O=), loc (L=) and ou (OU=) values in the dn. + * + * @param ou OrgUnit value (optional, use null to prevent filtering by ou) + * @param o value (optional, use null to prevent filtering by o) + * @param loc Locality value (optional, use null to prevent filtering by loc) + * @return + */ + public List findActiveUserAndRAOperatorBy(String ou, String o, String loc) { + long currentTime = Long.parseLong(getDateFormat().format(new Date())); + Map namedParameters = new HashMap<>(); + namedParameters.put("current_time", currentTime); + + StringBuilder query = new StringBuilder(SELECT_PROJECT) + .append("where (role = 'RA Operator' or role = 'User') "); + + String raFilter = buildRaFilter(ou, o, loc); + if (raFilter != null) { + namedParameters.put("ra", raFilter); + query.append("and dn like :ra "); + } + + query.append("and status = 'VALID' "); + //query.append("and notafter > :current_time"); + + return jdbcTemplate.query(query.toString(), namedParameters, new CertificateRowMapper()); + } + + private String buildRaFilter(String ou, String o, String loc) { + if (ou == null && o == null && loc == null) { + return null; + } + + StringJoiner raJoiner = new StringJoiner(",", "%", "%"); + + if (ou != null) { + raJoiner.add("OU=" + ou); + } + if (o != null) { + raJoiner.add("O=" + o); + } + if (loc != null) { + raJoiner.add("L=" + loc); + } + + return raJoiner.toString(); + } + + /** * Update the 'certificate' table with the values from the given CertificateRow. diff --git a/src/main/webapp/WEB-INF/jspf/header.jspf b/src/main/webapp/WEB-INF/jspf/header.jspf index cb24b4e..d4aaf14 100644 --- a/src/main/webapp/WEB-INF/jspf/header.jspf +++ b/src/main/webapp/WEB-INF/jspf/header.jspf @@ -37,6 +37,7 @@
  • Search New/Renew Signing Requests
  • Search Revocation Requests
  • Search Certificates
  • +
  • Assign RAOP role
  • diff --git a/src/main/webapp/WEB-INF/views/raop/displayuserraop.jsp b/src/main/webapp/WEB-INF/views/raop/displayuserraop.jsp new file mode 100644 index 0000000..947c04a --- /dev/null +++ b/src/main/webapp/WEB-INF/views/raop/displayuserraop.jsp @@ -0,0 +1,81 @@ + +<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1" %> +<%@ page session="false" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> + + + + + + + + RAOP role assignment + + + + <%@ include file="../../jspf/styles.jspf" %> + + + +<%@ include file="../../jspf/header.jspf" %> +
    +
    +
    + +

    List of users and RA operator for your RA [${ra}]

    +
    + + + + + + + + + + + + + + + + + +
    NameRoleRequestLast updated
    ${row.cn}${row.role} + + + + + + + + + ${row.notAfter}
    +
    +
    +
    +
    +<%@ include file="../../jspf/footer.jspf" %> + + + + \ No newline at end of file From 64878954ef8cf9adcec34644968e592c899e63d7 Mon Sep 17 00:00:00 2001 From: Manoj Garai Date: Thu, 18 Sep 2025 16:51:13 +0100 Subject: [PATCH 2/9] Rename enpoint to manage raop --- .../ac/ngs/controllers/DisplayUserRaop.java | 107 ----------- .../uk/ac/ngs/controllers/ManageRaop.java | 175 ++++++++++++++++++ src/main/webapp/WEB-INF/jspf/header.jspf | 2 +- .../{displayuserraop.jsp => manageraop.jsp} | 42 ++--- 4 files changed, 195 insertions(+), 131 deletions(-) delete mode 100644 src/main/java/uk/ac/ngs/controllers/DisplayUserRaop.java create mode 100644 src/main/java/uk/ac/ngs/controllers/ManageRaop.java rename src/main/webapp/WEB-INF/views/raop/{displayuserraop.jsp => manageraop.jsp} (64%) diff --git a/src/main/java/uk/ac/ngs/controllers/DisplayUserRaop.java b/src/main/java/uk/ac/ngs/controllers/DisplayUserRaop.java deleted file mode 100644 index 3494e17..0000000 --- a/src/main/java/uk/ac/ngs/controllers/DisplayUserRaop.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2015 STFC - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package uk.ac.ngs.controllers; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.security.access.annotation.Secured; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; - -import uk.ac.ngs.dao.JdbcCertificateDao; -import uk.ac.ngs.dao.JdbcCrrDao; -import uk.ac.ngs.dao.JdbcRaopListDao; -import uk.ac.ngs.dao.JdbcRequestDao; -import uk.ac.ngs.domain.CertificateRow; -import uk.ac.ngs.domain.CrrRow; -import uk.ac.ngs.domain.RaopListRow; -import uk.ac.ngs.domain.RequestRow; -import uk.ac.ngs.security.CaUser; -import uk.ac.ngs.security.SecurityContextService; -import uk.ac.ngs.service.CertUtil; - -import javax.inject.Inject; -import java.util.*; -import java.util.stream.Collectors; - -//import org.springframework.security.core.GrantedAuthority; - -/** - * @author David Meredith - */ -@Controller -@RequestMapping("/raop/displayuserraop") -@Secured("ROLE_RAOP") -public class DisplayUserRaop { - - private static final Log log = LogFactory.getLog(DisplayUserRaop.class); - private SecurityContextService securityContextService; - private JdbcRaopListDao jdbcRaopListDao; - private JdbcCertificateDao jdbcCertificateDao; - - public DisplayUserRaop(SecurityContextService securityContextService, JdbcRaopListDao jdbcRaopListDao, - JdbcCertificateDao jdbcCertificateDao) { - this.securityContextService = securityContextService; - this.jdbcRaopListDao = jdbcRaopListDao; - this.jdbcCertificateDao = jdbcCertificateDao; - } - - - @ModelAttribute - public void populateModel(Model model) { - log.debug("DisplayUserRaop populateModel"); - - // Get current user and their DN - CaUser caUser = securityContextService.getCaUserDetails(); - String currentUserDn = caUser.getCertificateRow().getDn(); - - // Extract DN attributes - String ou = CertUtil.extractDnAttribute(currentUserDn, CertUtil.DNAttributeType.OU); - String o = CertUtil.extractDnAttribute(currentUserDn, CertUtil.DNAttributeType.O); - String loc = CertUtil.extractDnAttribute(currentUserDn, CertUtil.DNAttributeType.L); - - // Build RA string and add to model - String ra = String.format("%s %s", ou, loc); - model.addAttribute("ra", ra); - log.debug("ra is:[" + ra + "]"); - - // Fetch active users and RA operators for the current RA - List userRaopRows = jdbcCertificateDao.findActiveUserAndRAOperatorBy(ou, o, loc); - - // Exclude current user from the list - List filteredRows = userRaopRows.stream() - .filter(row -> !currentUserDn.equals(row.getDn())) - .collect(Collectors.toList()); - - log.debug("Filtered User and RA Operator rows size: " + filteredRows.size()); - model.addAttribute("userRaopRows", filteredRows); - - // Add timestamp - model.addAttribute("lastPageRefreshDate", new Date()); -} - - - /** - * Select the raop/displayuserraop view to render. - * - * @return raop/displayuserraop - */ - @RequestMapping(method = RequestMethod.GET) - public String displayUserRaop(Locale locale, Model model) { - log.debug("Controller /raop/"); - return "raop/displayuserraop"; - } -} diff --git a/src/main/java/uk/ac/ngs/controllers/ManageRaop.java b/src/main/java/uk/ac/ngs/controllers/ManageRaop.java new file mode 100644 index 0000000..9bad444 --- /dev/null +++ b/src/main/java/uk/ac/ngs/controllers/ManageRaop.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2015 STFC + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package uk.ac.ngs.controllers; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.security.access.annotation.Secured; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; + +import uk.ac.ngs.dao.JdbcCertificateDao; +import uk.ac.ngs.domain.CertificateRow; +import uk.ac.ngs.security.CaUser; +import uk.ac.ngs.security.SecurityContextService; +import uk.ac.ngs.service.CertUtil; +import uk.ac.ngs.service.CertificateService; + +import java.io.IOException; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +@Controller +@RequestMapping("/raop/manageraop") +@Secured("ROLE_RAOP") +public class ManageRaop { + + private static final Log log = LogFactory.getLog(ManageRaop.class); + private SecurityContextService securityContextService; + private CertificateService certificateService; + private JdbcCertificateDao jdbcCertificateDao; + + private final static Pattern DATA_PROFILE_PATTERN = Pattern.compile("PROFILE\\s?=\\s?(\\w+)$", Pattern.MULTILINE); + + public ManageRaop(SecurityContextService securityContextService, + JdbcCertificateDao jdbcCertificateDao, CertificateService certificateService) { + this.securityContextService = securityContextService; + this.jdbcCertificateDao = jdbcCertificateDao; + this.certificateService = certificateService; + } + + + @ModelAttribute + public void populateModel(Model model) { + log.debug("ManageRaop populateModel"); + + // Get current user and their DN + CaUser caUser = securityContextService.getCaUserDetails(); + String currentUserDn = caUser.getCertificateRow().getDn(); + + // Extract DN attributes + String ou = CertUtil.extractDnAttribute(currentUserDn, CertUtil.DNAttributeType.OU); + String o = CertUtil.extractDnAttribute(currentUserDn, CertUtil.DNAttributeType.O); + String loc = CertUtil.extractDnAttribute(currentUserDn, CertUtil.DNAttributeType.L); + + // Build RA string and add to model + String ra = String.format("%s %s", ou, loc); + model.addAttribute("ra", ra); + log.debug("ra is:[" + ra + "]"); + + // Fetch active users and RA operators for the current RA + List userRaopRows = jdbcCertificateDao.findActiveUserAndRAOperatorBy(ou, o, loc); + + // Exclude current user from the list + List filteredRows = userRaopRows.stream() + .filter(row -> !currentUserDn.equals(row.getDn())) + .collect(Collectors.toList()); + + log.debug("Filtered User and RA Operator rows size: " + filteredRows.size()); + model.addAttribute("userRaopRows", filteredRows); + + // Add timestamp + model.addAttribute("lastPageRefreshDate", new Date()); +} + + + /** + * Select the raop/manageraop view to render. + * + * @return raop/manageraop + */ + @RequestMapping(method = RequestMethod.GET) + public String manageRaop(Locale locale, Model model) { + log.debug("Controller /raop/manageraop"); + return "raop/manageraop"; + } + + /** + * Handle POSTs to "/raop/manageraop/changeroletouser" to perform a demotion of + * certificate to User role by a RAOP. + *

    + * The view is always redirected and redirect attributes added as necessary; + * + * @param cert_key key of certificate being updated + * @param redirectAttrs + * @return + * @throws java.io.IOException + */ + @RequestMapping(value = "/changeroletouser", method = RequestMethod.POST) + public String changeRoleCertificate(@RequestParam long cert_key, RedirectAttributes redirectAttrs) + throws IOException { + CertificateRow targetCert = jdbcCertificateDao.findById(cert_key); + CertificateRow currentUser = securityContextService.getCaUserDetails().getCertificateRow(); + String message; + + if (viewerCanDemote(currentUser)) { + String newRole = "User"; + certificateService.updateCertificateRole(cert_key, newRole); + + log.info("Role change from (" + targetCert.getRole() + ") to (" + newRole + ")for certificate (" + + cert_key + ") by (" + currentUser.getDn() + ")"); + message = "Role Change OK"; + } else { + message = "Role Change FAIL - Viewer does not have correct permissions"; + } + + redirectAttrs.addFlashAttribute("responseMessage", message); + //redirectAttrs.addAttribute("certId", cert_key); + return "redirect:/raop/manageraop"; + } + + + /*** + * Check to see if a user can demote certificates. RA-OP ONLY for own RA + * + * @param viewCert certificate of the viewer + * @return result of check + */ + private boolean viewerCanDemote(CertificateRow cert) { + // Check if current user has RA Operator role + if (!securityContextService.getCaUserDetails().getAuthorities() + .contains(new SimpleGrantedAuthority("ROLE_RAOP"))) { + return false; + } + + // Extract profile from certificate data + String profile = "default"; + Matcher profileMatcher = DATA_PROFILE_PATTERN.matcher(cert.getData()); + if (profileMatcher.find()) { + profile = profileMatcher.group(1); + } + + // Only allow demotion for UKPERSON profile + if (!"UKPERSON".equals(profile)) { + return false; + } + + // Compare DN attributes (OU and L) between current user and target certificate + String currentUserDn = securityContextService.getCaUserDetails().getCertificateRow().getDn(); + String currentOU = CertUtil.extractDnAttribute(currentUserDn, CertUtil.DNAttributeType.OU); + String currentL = CertUtil.extractDnAttribute(currentUserDn, CertUtil.DNAttributeType.L); + + String certOU = CertUtil.extractDnAttribute(cert.getDn(), CertUtil.DNAttributeType.OU); + String certL = CertUtil.extractDnAttribute(cert.getDn(), CertUtil.DNAttributeType.L); + + return currentOU.equals(certOU) && currentL.equals(certL); + } +} diff --git a/src/main/webapp/WEB-INF/jspf/header.jspf b/src/main/webapp/WEB-INF/jspf/header.jspf index d4aaf14..617a456 100644 --- a/src/main/webapp/WEB-INF/jspf/header.jspf +++ b/src/main/webapp/WEB-INF/jspf/header.jspf @@ -37,7 +37,7 @@

  • Search New/Renew Signing Requests
  • Search Revocation Requests
  • Search Certificates
  • -
  • Assign RAOP role
  • +
  • Manage RAOP role
  • diff --git a/src/main/webapp/WEB-INF/views/raop/displayuserraop.jsp b/src/main/webapp/WEB-INF/views/raop/manageraop.jsp similarity index 64% rename from src/main/webapp/WEB-INF/views/raop/displayuserraop.jsp rename to src/main/webapp/WEB-INF/views/raop/manageraop.jsp index 947c04a..272bef4 100644 --- a/src/main/webapp/WEB-INF/views/raop/displayuserraop.jsp +++ b/src/main/webapp/WEB-INF/views/raop/manageraop.jsp @@ -3,7 +3,7 @@ <%@ page session="false" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> - +<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %> @@ -13,6 +13,7 @@ RAOP role assignment + <%@ include file="../../jspf/styles.jspf" %> @@ -24,6 +25,12 @@

    List of users and RA operator for your RA [${ra}]

    + +
    + ${responseMessage} +
    +
    +
    @@ -41,14 +48,19 @@ @@ -61,21 +73,5 @@ <%@ include file="../../jspf/footer.jspf" %> - - \ No newline at end of file From 3b893f0ab0daee966e75036291919df07e6a36ce Mon Sep 17 00:00:00 2001 From: Manoj Garai Date: Wed, 24 Sep 2025 10:55:52 +0100 Subject: [PATCH 3/9] Add table to manage RAOP request --- .../uk/ac/ngs/controllers/ManageRaop.java | 107 ++++++++--- .../java/uk/ac/ngs/controllers/RaOpHome.java | 180 ++++++++++++++++-- .../ngs/dao/RoleChangeRequestRepository.java | 14 ++ .../uk/ac/ngs/domain/RoleChangeRequest.java | 77 ++++++++ .../uk/ac/ngs/service/CertificateService.java | 19 ++ .../webapp/WEB-INF/views/raop/manageraop.jsp | 64 +++++-- .../webapp/WEB-INF/views/raop/raophome.jsp | 57 ++++++ 7 files changed, 465 insertions(+), 53 deletions(-) create mode 100644 src/main/java/uk/ac/ngs/dao/RoleChangeRequestRepository.java create mode 100644 src/main/java/uk/ac/ngs/domain/RoleChangeRequest.java diff --git a/src/main/java/uk/ac/ngs/controllers/ManageRaop.java b/src/main/java/uk/ac/ngs/controllers/ManageRaop.java index 9bad444..8c5f983 100644 --- a/src/main/java/uk/ac/ngs/controllers/ManageRaop.java +++ b/src/main/java/uk/ac/ngs/controllers/ManageRaop.java @@ -25,7 +25,9 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes; import uk.ac.ngs.dao.JdbcCertificateDao; +import uk.ac.ngs.dao.RoleChangeRequestRepository; import uk.ac.ngs.domain.CertificateRow; +import uk.ac.ngs.domain.RoleChangeRequest; import uk.ac.ngs.security.CaUser; import uk.ac.ngs.security.SecurityContextService; import uk.ac.ngs.service.CertUtil; @@ -46,14 +48,17 @@ public class ManageRaop { private SecurityContextService securityContextService; private CertificateService certificateService; private JdbcCertificateDao jdbcCertificateDao; + private RoleChangeRequestRepository roleChangeRequestRepository; private final static Pattern DATA_PROFILE_PATTERN = Pattern.compile("PROFILE\\s?=\\s?(\\w+)$", Pattern.MULTILINE); public ManageRaop(SecurityContextService securityContextService, - JdbcCertificateDao jdbcCertificateDao, CertificateService certificateService) { + JdbcCertificateDao jdbcCertificateDao, CertificateService certificateService, + RoleChangeRequestRepository roleChangeRequestRepository) { this.securityContextService = securityContextService; this.jdbcCertificateDao = jdbcCertificateDao; this.certificateService = certificateService; + this.roleChangeRequestRepository = roleChangeRequestRepository; } @@ -69,7 +74,8 @@ public void populateModel(Model model) { String ou = CertUtil.extractDnAttribute(currentUserDn, CertUtil.DNAttributeType.OU); String o = CertUtil.extractDnAttribute(currentUserDn, CertUtil.DNAttributeType.O); String loc = CertUtil.extractDnAttribute(currentUserDn, CertUtil.DNAttributeType.L); - + model.addAttribute("currentUserDn", currentUserDn); + // Build RA string and add to model String ra = String.format("%s %s", ou, loc); model.addAttribute("ra", ra); @@ -79,12 +85,19 @@ public void populateModel(Model model) { List userRaopRows = jdbcCertificateDao.findActiveUserAndRAOperatorBy(ou, o, loc); // Exclude current user from the list - List filteredRows = userRaopRows.stream() - .filter(row -> !currentUserDn.equals(row.getDn())) - .collect(Collectors.toList()); + // List filteredRows = userRaopRows.stream() + // .filter(row -> !currentUserDn.equals(row.getDn())) + // .collect(Collectors.toList()); + + // log.debug("Filtered User and RA Operator rows size: " + filteredRows.size()); + model.addAttribute("userRaopRows", userRaopRows); + + List listOfRoleChangeRequestCertKey = roleChangeRequestRepository.findAll() + .stream() + .map(RoleChangeRequest::getCertKey) + .collect(Collectors.toList()); - log.debug("Filtered User and RA Operator rows size: " + filteredRows.size()); - model.addAttribute("userRaopRows", filteredRows); + model.addAttribute("listOfRoleChangeRequestCertKey", listOfRoleChangeRequestCertKey); // Add timestamp model.addAttribute("lastPageRefreshDate", new Date()); @@ -120,56 +133,102 @@ public String changeRoleCertificate(@RequestParam long cert_key, RedirectAttribu CertificateRow currentUser = securityContextService.getCaUserDetails().getCertificateRow(); String message; - if (viewerCanDemote(currentUser)) { + if (canViewerManageRaopRole(targetCert)) { String newRole = "User"; certificateService.updateCertificateRole(cert_key, newRole); log.info("Role change from (" + targetCert.getRole() + ") to (" + newRole + ")for certificate (" + cert_key + ") by (" + currentUser.getDn() + ")"); - message = "Role Change OK"; + message = "Role updated successfully!"; + } else { + message = "Role Change FAIL - user does not have correct permissions"; + } + + redirectAttrs.addFlashAttribute("responseMessage", message); + return "redirect:/raop/manageraop"; + } + + /** + * Handle POSTs to "/raop/manageraop/requestraoprole" to request RAOP role of + * certificate from User role by a RAOP. + *

    + * The view is always redirected and redirect attributes added as necessary; + * + * @param cert_key key of certificate being updated + * @param redirectAttrs + * @return + * @throws java.io.IOException + */ + @RequestMapping(value = "/requestraoprole", method = RequestMethod.POST) + public String requestRaopRole(@RequestParam long cert_key, RedirectAttributes redirectAttrs) + throws IOException { + CertificateRow targetCert = jdbcCertificateDao.findById(cert_key); + CertificateRow currentUser = securityContextService.getCaUserDetails().getCertificateRow(); + String message; + + if (canViewerManageRaopRole(targetCert)) { + String newRole = "RA Operator"; + certificateService.raiseRoleChangeRequest(cert_key, newRole, currentUser); + + log.info("Role change request from (" + targetCert.getRole() + ") to (" + newRole + ")for certificate (" + + cert_key + ") by (" + currentUser.getDn() + ") has been raised"); + message = "A role change request has been raised successfully"; } else { - message = "Role Change FAIL - Viewer does not have correct permissions"; + message = "Role change request FAIL - user does not have correct permissions"; } redirectAttrs.addFlashAttribute("responseMessage", message); - //redirectAttrs.addAttribute("certId", cert_key); return "redirect:/raop/manageraop"; } /*** - * Check to see if a user can demote certificates. RA-OP ONLY for own RA + * Check to see if a user can request role change of certificate. RAOP ONLY for own RA * - * @param viewCert certificate of the viewer + * @param targetCert certificate subject to role chnage * @return result of check */ - private boolean viewerCanDemote(CertificateRow cert) { + private boolean canViewerManageRaopRole(CertificateRow targetCert) { + String currentUserDn = securityContextService.getCaUserDetails().getCertificateRow().getDn(); + // Check if current user has RA Operator role if (!securityContextService.getCaUserDetails().getAuthorities() .contains(new SimpleGrantedAuthority("ROLE_RAOP"))) { return false; } + // Check if request is for own certificate + if (currentUserDn.equals(targetCert.getDn())) { + return false; + } + + // Validate certificate data + if (targetCert == null || targetCert.getData() == null) { + log.warn("Target certificate or its data is null"); + return false; + } + // Extract profile from certificate data - String profile = "default"; - Matcher profileMatcher = DATA_PROFILE_PATTERN.matcher(cert.getData()); - if (profileMatcher.find()) { - profile = profileMatcher.group(1); + Matcher profileMatcher = DATA_PROFILE_PATTERN.matcher(targetCert.getData()); + if (!profileMatcher.find()) { + log.warn("Profile not found in certificate data for certKey: " + targetCert.getCert_key()); + return false; } + String profile = profileMatcher.group(1); + // Only allow demotion for UKPERSON profile if (!"UKPERSON".equals(profile)) { return false; } // Compare DN attributes (OU and L) between current user and target certificate - String currentUserDn = securityContextService.getCaUserDetails().getCertificateRow().getDn(); - String currentOU = CertUtil.extractDnAttribute(currentUserDn, CertUtil.DNAttributeType.OU); - String currentL = CertUtil.extractDnAttribute(currentUserDn, CertUtil.DNAttributeType.L); + String currentUserOU = CertUtil.extractDnAttribute(currentUserDn, CertUtil.DNAttributeType.OU); + String currentUserL = CertUtil.extractDnAttribute(currentUserDn, CertUtil.DNAttributeType.L); - String certOU = CertUtil.extractDnAttribute(cert.getDn(), CertUtil.DNAttributeType.OU); - String certL = CertUtil.extractDnAttribute(cert.getDn(), CertUtil.DNAttributeType.L); + String targetCertOU = CertUtil.extractDnAttribute(targetCert.getDn(), CertUtil.DNAttributeType.OU); + String targetCertL = CertUtil.extractDnAttribute(targetCert.getDn(), CertUtil.DNAttributeType.L); - return currentOU.equals(certOU) && currentL.equals(certL); + return currentUserOU.equals(targetCertOU) && currentUserL.equals(targetCertL); } } diff --git a/src/main/java/uk/ac/ngs/controllers/RaOpHome.java b/src/main/java/uk/ac/ngs/controllers/RaOpHome.java index 9a27d62..bc2e2db 100644 --- a/src/main/java/uk/ac/ngs/controllers/RaOpHome.java +++ b/src/main/java/uk/ac/ngs/controllers/RaOpHome.java @@ -15,23 +15,36 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.security.access.annotation.Secured; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; + +import uk.ac.ngs.dao.JdbcCertificateDao; import uk.ac.ngs.dao.JdbcCrrDao; import uk.ac.ngs.dao.JdbcRaopListDao; import uk.ac.ngs.dao.JdbcRequestDao; +import uk.ac.ngs.dao.RoleChangeRequestRepository; +import uk.ac.ngs.domain.CertificateRow; import uk.ac.ngs.domain.CrrRow; import uk.ac.ngs.domain.RaopListRow; import uk.ac.ngs.domain.RequestRow; +import uk.ac.ngs.domain.RoleChangeRequest; import uk.ac.ngs.security.CaUser; import uk.ac.ngs.security.SecurityContextService; import uk.ac.ngs.service.CertUtil; +import uk.ac.ngs.service.CertificateService; import javax.inject.Inject; + +import java.io.IOException; import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; //import org.springframework.security.core.GrantedAuthority; @@ -48,6 +61,11 @@ public class RaOpHome { private JdbcRaopListDao jdbcRaopListDao; private JdbcRequestDao jdbcRequestDao; private JdbcCrrDao jdbcCrrDao; + private RoleChangeRequestRepository roleChangeRequestRepository; + private JdbcCertificateDao jdbcCertificateDao; + private CertificateService certificateService; + + private final static Pattern DATA_PROFILE_PATTERN = Pattern.compile("PROFILE\\s?=\\s?(\\w+)$", Pattern.MULTILINE); public RaOpHome() { log.debug(RaOpHome.class.getName() + " ***created dave***"); @@ -58,29 +76,29 @@ public RaOpHome() { public void populateModel(Model model) { log.debug("raop populateModel"); - // Get the current CaUser (so we can extract their DN) + // Get the current CaUser (so we can extract their DN) CaUser caUser = securityContextService.getCaUserDetails(); - //Collection auths = caUser.getAuthorities(); + // Collection auths = caUser.getAuthorities(); - //model.addAttribute("caUser", caUser); + // model.addAttribute("caUser", caUser); // Extract the RA value from the user's certificate DN String dn = caUser.getCertificateRow().getDn(); - String OU = CertUtil.extractDnAttribute(dn, CertUtil.DNAttributeType.OU); //CLRC - String L = CertUtil.extractDnAttribute(dn, CertUtil.DNAttributeType.L); //RAL - String CN = CertUtil.extractDnAttribute(dn, CertUtil.DNAttributeType.CN); //meryem tahar + String OU = CertUtil.extractDnAttribute(dn, CertUtil.DNAttributeType.OU); // CLRC + String L = CertUtil.extractDnAttribute(dn, CertUtil.DNAttributeType.L); // RAL + String CN = CertUtil.extractDnAttribute(dn, CertUtil.DNAttributeType.CN); // meryem tahar String ra = OU + " " + L; model.addAttribute("ra", ra); log.debug("ra is:[" + ra + "]"); - // Get the current user's RAOP details for display (i.e. when they last - // did the training etc). Note, this does not affect their ability to - // do raop stuff in the portal. + // Get the current user's RAOP details for display (i.e. when they last + // did the training etc). Note, this does not affect their ability to + // do raop stuff in the portal. List raoprows = this.jdbcRaopListDao.findBy(OU, L, CN, true); log.debug("raoprows size: " + raoprows.size()); model.addAttribute("raoprows", raoprows); - // Fetch a list of pending CSRs for the RA (NEW and RENEW) + // Fetch a list of pending CSRs for the RA (NEW and RENEW) // NEW Map whereParams = new HashMap<>(); whereParams.put(JdbcRequestDao.WHERE_PARAMS.RA_EQ, ra); @@ -94,18 +112,141 @@ public void populateModel(Model model) { renewRequestRows = jdbcRequestDao.setDataNotBefore(renewRequestRows); model.addAttribute("renew_reqrows", renewRequestRows); - // Fetch a list of pending CRRs for the RA + // Fetch a list of pending CRRs for the RA Map crrWhereParams = new HashMap<>(); - crrWhereParams.put(JdbcCrrDao.WHERE_PARAMS.STATUS_EQ, "NEW"); //NEW,APPROVED,ARCHIVED,DELETED + crrWhereParams.put(JdbcCrrDao.WHERE_PARAMS.STATUS_EQ, "NEW"); // NEW,APPROVED,ARCHIVED,DELETED crrWhereParams.put(JdbcCrrDao.WHERE_PARAMS.DN_LIKE, "%L=" + L + ",OU=" + OU + "%"); List crrRows = jdbcCrrDao.findBy(crrWhereParams, null, null); log.debug("crrRows size: [" + crrRows.size() + "]"); crrRows = jdbcCrrDao.setSubmitDateFromData(crrRows); model.addAttribute("crr_reqrows", crrRows); + // Fetch list of pending RA operator role assignment requests for CAOP + if (caUser.getAuthorities() + .contains(new SimpleGrantedAuthority("ROLE_CAOP"))) { + List roleChangeRequests = roleChangeRequestRepository.findAll(); + model.addAttribute("roleChangeRequests", roleChangeRequests); + } + model.addAttribute("lastPageRefreshDate", new Date()); } + /** + * Handle POSTs to "/raop/approverolechange" to approve a role change request of + * certificate to RAOP role by a CAOP. + *

    + * The view is always redirected and redirect attributes added as necessary; + * + * @param cert_key key of certificate being updated + * @param requestId id of the role change request + * @param redirectAttrs + * @return + * @throws java.io.IOException + */ + @Secured("ROLE_CAOP") + @RequestMapping(value = "/approverolechange", method = RequestMethod.POST) + public String approverolechange(@RequestParam long certKey, @RequestParam Integer requestId, + RedirectAttributes redirectAttrs) + throws IOException { + String message; + + try { + CertificateRow targetCert = jdbcCertificateDao.findById(certKey); + CertificateRow currentUser = securityContextService.getCaUserDetails().getCertificateRow(); + + if (targetCert == null) { + message = "Role Change FAIL - Certificate not found for certKey: " + certKey; + log.warn(message); + } else if (!canUserManageRaopRole(targetCert)) { + message = "Role Change FAIL - user does not have correct permissions"; + log.warn("Unauthorized role change attempt by:" + currentUser.getDn()); + } else { + String newRole = "RA Operator"; + certificateService.updateCertificateRole(certKey, newRole); + roleChangeRequestRepository.deleteById(requestId); + + log.info("Role change from (" + targetCert.getRole() + ") to (" + newRole + ") for certificate (" + + certKey + ") by (" + currentUser.getDn() + ")"); + + message = "Role updated successfully!"; + } + } catch (Exception e) { + log.error("Error during role change approval for certKey " + certKey + " and requestId " + requestId + ": " + + e.getMessage()); + message = "Role Change FAIL - Internal error occurred"; + } + + redirectAttrs.addFlashAttribute("responseMessage", message); + return "redirect:/raop"; + } + + + /** + * Handle POSTs to "/raop/rejectrolechange" to reject + * certificate role change by a CAOP. + *

    + * The view is always redirected and redirect attributes added as necessary; + * + * @param requestId id of the request raised by RAOP + * @param redirectAttrs + * @return + * @throws java.io.IOException + */ + @Secured("ROLE_CAOP") + @RequestMapping(value = "/rejectrolechange", method = RequestMethod.POST) + public String rejectrolechange(@RequestParam Integer requestId, RedirectAttributes redirectAttrs) + throws IOException { + String message; + + try { + roleChangeRequestRepository.deleteById(requestId); + message = "Request rejected successfully!"; + } catch (Exception e) { + message = "Request rejection failed!"; + e.printStackTrace(); + } + + redirectAttrs.addFlashAttribute("responseMessage", message); + return "redirect:/raop"; + } + + /*** + * Check to see if a user can manage certificates role change. + * + * @param targetCert role change certificate + * @return result of check + */ + private boolean canUserManageRaopRole(CertificateRow targetCert) { + // Check if current user has CA Operator management authority + if (!securityContextService.getCaUserDetails().getAuthorities() + .contains(new SimpleGrantedAuthority("ROLE_CAOP"))) { + return false; + } + + // Validate certificate data + if (targetCert == null || targetCert.getData() == null) { + log.warn("Target certificate or its data is null"); + return false; + } + + // Extract profile from certificate data + Matcher profileMatcher = DATA_PROFILE_PATTERN.matcher(targetCert.getData()); + if (!profileMatcher.find()) { + log.warn("Profile not found in certificate data for certKey: " + targetCert.getCert_key()); + return false; + } + + String profile = profileMatcher.group(1); + + // Only allow role change for UKPERSON profile + boolean isAllowed = "UKPERSON".equals(profile); + if (!isAllowed) { + log.info("Role change not allowed for profile:" + profile); + } + + return isAllowed; + } + /** * Select the raop/home view to render. * @@ -136,4 +277,19 @@ public void setJdbcRequestDao(JdbcRequestDao jdbcRequestDao) { public void setJdbcCrrDao(JdbcCrrDao jdbcCrrDao) { this.jdbcCrrDao = jdbcCrrDao; } + + @Inject + public void setRoleChangeRequestRepository(RoleChangeRequestRepository roleChangeRequestRepository) { + this.roleChangeRequestRepository = roleChangeRequestRepository; + } + + @Inject + public void setRoleChangeRequestRepository(JdbcCertificateDao jdbcCertificateDao) { + this.jdbcCertificateDao = jdbcCertificateDao; + } + + @Inject + public void setRoleChangeRequestRepository(CertificateService certificateService) { + this.certificateService = certificateService; + } } diff --git a/src/main/java/uk/ac/ngs/dao/RoleChangeRequestRepository.java b/src/main/java/uk/ac/ngs/dao/RoleChangeRequestRepository.java new file mode 100644 index 0000000..c5c88c9 --- /dev/null +++ b/src/main/java/uk/ac/ngs/dao/RoleChangeRequestRepository.java @@ -0,0 +1,14 @@ +package uk.ac.ngs.dao; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +import uk.ac.ngs.domain.RoleChangeRequest; + +import java.util.List; + +@Repository +public interface RoleChangeRequestRepository extends CrudRepository { + List findAll(); + void deleteById(Integer id); +} diff --git a/src/main/java/uk/ac/ngs/domain/RoleChangeRequest.java b/src/main/java/uk/ac/ngs/domain/RoleChangeRequest.java new file mode 100644 index 0000000..d4c134d --- /dev/null +++ b/src/main/java/uk/ac/ngs/domain/RoleChangeRequest.java @@ -0,0 +1,77 @@ +package uk.ac.ngs.domain; + +import org.springframework.data.annotation.Id; +import org.springframework.data.relational.core.mapping.Table; +import org.springframework.data.relational.core.mapping.Column; + +import java.time.LocalDate; + +@Table("role_change_request") +public class RoleChangeRequest { + + @Id + private Integer id; + + @Column("cert_key") + private Long certKey; + + @Column("requested_role") + private String requestedRole; + + @Column("requested_by") + private String requestedBy; + + @Column("requested_on") + private LocalDate requestedOn; + + // Constructors + public RoleChangeRequest() {} + + public RoleChangeRequest(Long certKey, String requestedRole, String requestedBy, LocalDate requestedOn) { + this.certKey = certKey; + this.requestedRole = requestedRole; + this.requestedBy = requestedBy; + this.requestedOn = requestedOn; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Long getCertKey() { + return certKey; + } + + public void setCertKey(Long certKey) { + this.certKey = certKey; + } + + public String getRequestedRole() { + return requestedRole; + } + + public void setRequestedRole(String requestedRole) { + this.requestedRole = requestedRole; + } + + public String getRequestedBy() { + return requestedBy; + } + + public void setRequestedBy(String requestedBy) { + this.requestedBy = requestedBy; + } + + public LocalDate getRequestedOn() { + return requestedOn; + } + + public void setRequestedOn(LocalDate requestedOn) { + this.requestedOn = requestedOn; + } +} + diff --git a/src/main/java/uk/ac/ngs/service/CertificateService.java b/src/main/java/uk/ac/ngs/service/CertificateService.java index 1ef618b..5530a3e 100644 --- a/src/main/java/uk/ac/ngs/service/CertificateService.java +++ b/src/main/java/uk/ac/ngs/service/CertificateService.java @@ -20,12 +20,15 @@ import org.springframework.validation.MapBindingResult; import uk.ac.ngs.common.MutableConfigParams; import uk.ac.ngs.dao.JdbcCertificateDao; +import uk.ac.ngs.dao.RoleChangeRequestRepository; import uk.ac.ngs.domain.CertificateRow; +import uk.ac.ngs.domain.RoleChangeRequest; import uk.ac.ngs.service.email.EmailService; import uk.ac.ngs.validation.EmailValidator; import javax.inject.Inject; import java.io.IOException; +import java.time.LocalDate; import java.util.Date; import java.util.EnumMap; import java.util.HashMap; @@ -46,6 +49,8 @@ public class CertificateService { private JdbcCertificateDao jdbcCertDao; private EmailService emailService; private MutableConfigParams mutableConfigParams; + private RoleChangeRequestRepository roleChangeRequestRepository; + private final static int flags = Pattern.CASE_INSENSITIVE | Pattern.MULTILINE; //pattern to match 'emailAddress' in cert's data field private final static Pattern DATA_EMAIL_PATTERN = Pattern.compile("emailAddress\\s?=\\s?([^\\n]+)$", flags); @@ -205,6 +210,15 @@ private int updateCertificateRoleHelper(long cert_key, String new_cert_role) return jdbcCertDao.updateCertificateRow(certRow); } + + public void raiseRoleChangeRequest(long cert_key, String newRole, CertificateRow currentUser) { + RoleChangeRequest roleChangeRequest = new RoleChangeRequest(cert_key, newRole, currentUser.getCn(), + LocalDate.now()); + + RoleChangeRequest saved = roleChangeRequestRepository.save(roleChangeRequest); + log.debug("Role change request saved with ID: " + saved.getId()); + } + @Inject public void setJdbcCertificateDao(JdbcCertificateDao jdbcCertDao) { this.jdbcCertDao = jdbcCertDao; @@ -219,4 +233,9 @@ public void setEmailService(EmailService emailService) { public void setMutableConfigParams(MutableConfigParams mutableConfigParams) { this.mutableConfigParams = mutableConfigParams; } + + @Inject + public void setRoleChangeRequestRepository(RoleChangeRequestRepository roleChangeRequestRepository) { + this.roleChangeRequestRepository = roleChangeRequestRepository; + } } diff --git a/src/main/webapp/WEB-INF/views/raop/manageraop.jsp b/src/main/webapp/WEB-INF/views/raop/manageraop.jsp index 272bef4..cab1b9f 100644 --- a/src/main/webapp/WEB-INF/views/raop/manageraop.jsp +++ b/src/main/webapp/WEB-INF/views/raop/manageraop.jsp @@ -10,10 +10,9 @@ - RAOP role assignment + Manage RAOP role - - + <%@ include file="../../jspf/styles.jspf" %> @@ -25,19 +24,25 @@

    List of users and RA operator for your RA [${ra}]

    + Last Refreshed: ${lastPageRefreshDate} + +
    ${responseMessage}
    + +


    +
    -
    +
    - + + + + + - - +
    - + @@ -45,22 +50,47 @@ - diff --git a/src/main/webapp/WEB-INF/views/raop/raophome.jsp b/src/main/webapp/WEB-INF/views/raop/raophome.jsp index 9b545be..f82fac7 100644 --- a/src/main/webapp/WEB-INF/views/raop/raophome.jsp +++ b/src/main/webapp/WEB-INF/views/raop/raophome.jsp @@ -3,6 +3,7 @@ <%@ page session="false" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %> +<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %> @@ -95,6 +96,62 @@ + + +
    +
    +
    + ${responseMessage} +
    +
    + +
    +
    +
    +

    Pending requests for role change

    +
    +
    Name RoleRequestAction Last updated
    ${row.cn} ${row.role} + - - - - - + + + + + + + + + + + + - + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + +
    CertificateRequested roleRequested byRequested OnAction
    ${row.certKey} + ${row.requestedRole}${row.requestedBy}${row.requestedOn} +
    + + + + + + + + + + + + +
    +
    +
    +
    + <%----%> <%@ include file="../../jspf/footer.jspf" %> From 34c057795ab467aea07281e7c216b15a35ecbff5 Mon Sep 17 00:00:00 2001 From: Manoj Garai Date: Tue, 14 Oct 2025 14:01:43 +0100 Subject: [PATCH 4/9] Send email for new RAOP role request --- src/main/java/uk/ac/ngs/WebConfig.java | 3 + .../uk/ac/ngs/controllers/ManageRaop.java | 14 +-- .../java/uk/ac/ngs/controllers/RaOpHome.java | 2 +- .../uk/ac/ngs/dao/JdbcCertificateDao.java | 15 +++ .../ngs/dao/RoleChangeRequestRepository.java | 3 + .../uk/ac/ngs/service/CertificateService.java | 27 +++++- .../uk/ac/ngs/service/email/EmailService.java | 93 ++++++++++++++++++- .../emailAdminsOnRaopRoleRequestTemplate.html | 25 +++++ .../emailRaOnRaopRoleRequestTemplate.html | 26 ++++++ .../emailUserOnRaopRoleRequestTemplate.html | 26 ++++++ 10 files changed, 222 insertions(+), 12 deletions(-) create mode 100644 src/main/webapp/WEB-INF/freemarker/email/emailAdminsOnRaopRoleRequestTemplate.html create mode 100644 src/main/webapp/WEB-INF/freemarker/email/emailRaOnRaopRoleRequestTemplate.html create mode 100644 src/main/webapp/WEB-INF/freemarker/email/emailUserOnRaopRoleRequestTemplate.html diff --git a/src/main/java/uk/ac/ngs/WebConfig.java b/src/main/java/uk/ac/ngs/WebConfig.java index 61fdbbc..01a0c88 100644 --- a/src/main/java/uk/ac/ngs/WebConfig.java +++ b/src/main/java/uk/ac/ngs/WebConfig.java @@ -114,6 +114,9 @@ public EmailService emailService() { emailService.setEmailAdminsOnErrorTemplate("emailAdminsOnErrorTemplate.html"); emailService.setEmailRaEmailChangeTemplate("emailRaEmailChangeTemplate.html"); emailService.setEmailRaHostRenewTemplate("emailRaHostRenewTemplate.html"); + emailService.setEmailAdminsOnRaopRoleRequestTemplate("emailAdminsOnRaopRoleRequestTemplate.html"); + emailService.setEmailRaOnRaopRoleRequestTemplate("emailRaOnRaopRoleRequestTemplate.html"); + emailService.setEmailUserOnRaopRoleRequestTemplate("emailUserOnRaopRoleRequestTemplate.html"); emailService.setBasePortalUrl(basePortalUrl); return emailService; } diff --git a/src/main/java/uk/ac/ngs/controllers/ManageRaop.java b/src/main/java/uk/ac/ngs/controllers/ManageRaop.java index 8c5f983..a09320d 100644 --- a/src/main/java/uk/ac/ngs/controllers/ManageRaop.java +++ b/src/main/java/uk/ac/ngs/controllers/ManageRaop.java @@ -75,7 +75,7 @@ public void populateModel(Model model) { String o = CertUtil.extractDnAttribute(currentUserDn, CertUtil.DNAttributeType.O); String loc = CertUtil.extractDnAttribute(currentUserDn, CertUtil.DNAttributeType.L); model.addAttribute("currentUserDn", currentUserDn); - + // Build RA string and add to model String ra = String.format("%s %s", ou, loc); model.addAttribute("ra", ra); @@ -168,7 +168,7 @@ public String requestRaopRole(@RequestParam long cert_key, RedirectAttributes re if (canViewerManageRaopRole(targetCert)) { String newRole = "RA Operator"; - certificateService.raiseRoleChangeRequest(cert_key, newRole, currentUser); + certificateService.raiseRoleChangeRequest(cert_key, targetCert, newRole, currentUser); log.info("Role change request from (" + targetCert.getRole() + ") to (" + newRole + ")for certificate (" + cert_key + ") by (" + currentUser.getDn() + ") has been raised"); @@ -197,17 +197,17 @@ private boolean canViewerManageRaopRole(CertificateRow targetCert) { return false; } - // Check if request is for own certificate - if (currentUserDn.equals(targetCert.getDn())) { - return false; - } - // Validate certificate data if (targetCert == null || targetCert.getData() == null) { log.warn("Target certificate or its data is null"); return false; } + // Check if request is for own certificate + if (currentUserDn.equals(targetCert.getDn())) { + return false; + } + // Extract profile from certificate data Matcher profileMatcher = DATA_PROFILE_PATTERN.matcher(targetCert.getData()); if (!profileMatcher.find()) { diff --git a/src/main/java/uk/ac/ngs/controllers/RaOpHome.java b/src/main/java/uk/ac/ngs/controllers/RaOpHome.java index bc2e2db..acef35c 100644 --- a/src/main/java/uk/ac/ngs/controllers/RaOpHome.java +++ b/src/main/java/uk/ac/ngs/controllers/RaOpHome.java @@ -137,7 +137,7 @@ public void populateModel(Model model) { *

    * The view is always redirected and redirect attributes added as necessary; * - * @param cert_key key of certificate being updated + * @param certKey key of certificate being updated * @param requestId id of the role change request * @param redirectAttrs * @return diff --git a/src/main/java/uk/ac/ngs/dao/JdbcCertificateDao.java b/src/main/java/uk/ac/ngs/dao/JdbcCertificateDao.java index edb9bd7..51c81a6 100644 --- a/src/main/java/uk/ac/ngs/dao/JdbcCertificateDao.java +++ b/src/main/java/uk/ac/ngs/dao/JdbcCertificateDao.java @@ -285,6 +285,21 @@ public List findActiveRAsBy(String loc, String ou) { return this.jdbcTemplate.query(query.toString(), namedParameters, new CertificateRowMapper()); } + /** + * Find all rows with role 'CA Operator' with a + * status of 'VALID' and a 'notafter' time that is in the future + */ + public List findActiveCAs() { + String currentTime = getDateFormat().format(new Date()); + Map namedParameters = new HashMap<>(); + namedParameters.put("current_time", Long.parseLong(currentTime)); + + StringBuilder query = new StringBuilder(SELECT_PROJECT); + query.append("where role='CA Operator' "); + query.append("and status='VALID' and notafter > :current_time"); + return this.jdbcTemplate.query(query.toString(), namedParameters, new CertificateRowMapper()); + } + /** * Find all rows with role 'RA Operator' or 'User' with a * status of 'VALID' and a 'notafter' time that is in the future with the diff --git a/src/main/java/uk/ac/ngs/dao/RoleChangeRequestRepository.java b/src/main/java/uk/ac/ngs/dao/RoleChangeRequestRepository.java index c5c88c9..207393e 100644 --- a/src/main/java/uk/ac/ngs/dao/RoleChangeRequestRepository.java +++ b/src/main/java/uk/ac/ngs/dao/RoleChangeRequestRepository.java @@ -9,6 +9,9 @@ @Repository public interface RoleChangeRequestRepository extends CrudRepository { + @Override List findAll(); + + @Override void deleteById(Integer id); } diff --git a/src/main/java/uk/ac/ngs/service/CertificateService.java b/src/main/java/uk/ac/ngs/service/CertificateService.java index 5530a3e..a8b4535 100644 --- a/src/main/java/uk/ac/ngs/service/CertificateService.java +++ b/src/main/java/uk/ac/ngs/service/CertificateService.java @@ -32,6 +32,7 @@ import java.util.Date; import java.util.EnumMap; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -211,12 +212,32 @@ private int updateCertificateRoleHelper(long cert_key, String new_cert_role) } - public void raiseRoleChangeRequest(long cert_key, String newRole, CertificateRow currentUser) { + public void raiseRoleChangeRequest(long cert_key, CertificateRow targetCert, String newRole, + CertificateRow currentUser) { RoleChangeRequest roleChangeRequest = new RoleChangeRequest(cert_key, newRole, currentUser.getCn(), LocalDate.now()); - RoleChangeRequest saved = roleChangeRequestRepository.save(roleChangeRequest); - log.debug("Role change request saved with ID: " + saved.getId()); + RoleChangeRequest savedRequest = roleChangeRequestRepository.save(roleChangeRequest); + log.debug("Role change request saved with ID: " + savedRequest.getId()); + + sendEmailNotification(targetCert, currentUser); + } + + private void sendEmailNotification(CertificateRow targetCert, CertificateRow currentUser) { + List activeCAs = this.jdbcCertDao.findActiveCAs(); + + String requesterCn = currentUser.getCn(); + String requesterEmail = currentUser.getEmail(); + String targetDn = targetCert.getDn(); + String targetCn = targetCert.getCn(); + String requestedEmail = targetCert.getEmail(); + + for (CertificateRow ca : activeCAs) { + String adminEmail = ca.getEmail(); + this.emailService.sendAdminsOnRaopRoleRequest(requesterCn, targetDn, adminEmail); + } + this.emailService.sendRaOnRaopRoleRequest(requesterCn, targetDn, requesterEmail); + this.emailService.sendUserOnRaopRoleRequest(requesterCn, targetCn, requestedEmail); } @Inject diff --git a/src/main/java/uk/ac/ngs/service/email/EmailService.java b/src/main/java/uk/ac/ngs/service/email/EmailService.java index 1beca87..00e29d1 100644 --- a/src/main/java/uk/ac/ngs/service/email/EmailService.java +++ b/src/main/java/uk/ac/ngs/service/email/EmailService.java @@ -48,7 +48,10 @@ public class EmailService { private String emailAdminsRoleChangeTemplate; private String emailAdminsOnErrorTemplate; private String emailAdminsOnNewRaTemplate; - private String emailRaHostRenewTemplate; + private String emailRaHostRenewTemplate; + private String emailAdminsOnRaopRoleRequestTemplate; + private String emailRaOnRaopRoleRequestTemplate; + private String emailUserOnRaopRoleRequestTemplate; private String basePortalUrl; public EmailService() { @@ -365,6 +368,73 @@ public void sendAdminEmailOnRoleChange(String certDn, String role, long cert_key log.debug(emailDebug); } + /** + * Email the CAOP on user to RAOP role change request. + * + * @param requestorCN The CN/name of the requestor. + * @param requestedDN the requested cert DN + * @param recipientEmail + */ + public void sendAdminsOnRaopRoleRequest(String requesterCN, String requestedDN, String recipientEmail) { + SimpleMailMessage msg = new SimpleMailMessage(this.emailTemplate); + msg.setTo(recipientEmail); + Map vars = new HashMap<>(); + + vars.put("requesterCN", requesterCN); + vars.put("requestedDN", requestedDN); + vars.put("basePortalUrl", basePortalUrl); + + try { + this.mailSender.send(msg, vars, this.emailAdminsOnRaopRoleRequestTemplate); + } catch (MailException ex) { + log.error("MailSender " + ex.getMessage()); + } + } + + /** + * Email the RAOP on user to RAOP role change request. + * + * @param requesterCN The CN/name of the requester. + * @param requestedDN the requested cert DN + * @param recipientEmail + */ + public void sendRaOnRaopRoleRequest(String requesterCN, String requestedDN, String recipientEmail) { + SimpleMailMessage msg = new SimpleMailMessage(this.emailTemplate); + msg.setTo(recipientEmail); + Map vars = new HashMap<>(); + + vars.put("requesterCN", requesterCN); + vars.put("requestedDN", requestedDN); + + try { + this.mailSender.send(msg, vars, this.emailRaOnRaopRoleRequestTemplate); + } catch (MailException ex) { + log.error("MailSender " + ex.getMessage()); + } + } + + /** + * Email the user on user to RAOP role change request. + * + * @param requesterCN The CN/name of the requester. + * @param requestedCN the requested cert CN + * @param recipientEmail + */ + public void sendUserOnRaopRoleRequest(String requesterCN, String requestedCN, String recipientEmail) { + SimpleMailMessage msg = new SimpleMailMessage(this.emailTemplate); + msg.setTo(recipientEmail); + Map vars = new HashMap<>(); + + vars.put("requesterCN", requesterCN); + vars.put("requestedCN", requestedCN); + + try { + this.mailSender.send(msg, vars, this.emailUserOnRaopRoleRequestTemplate); + } catch (MailException ex) { + log.error("MailSender " + ex.getMessage()); + } + } + @Inject public void setSender(Sender mailSender) { this.mailSender = mailSender; @@ -459,4 +529,25 @@ public void setEmailAdminsOnErrorTemplate(String emailAdminsOnErrorTemplate) { this.emailAdminsOnErrorTemplate = emailAdminsOnErrorTemplate; } + /** + * @param emailAdminsOnRaopRoleRequestTemplate the emailAdminsOnRaopRoleRequestTemplate to set + */ + public void setEmailAdminsOnRaopRoleRequestTemplate(String emailAdminsOnRaopRoleRequestTemplate) { + this.emailAdminsOnRaopRoleRequestTemplate = emailAdminsOnRaopRoleRequestTemplate; + } + + /** + * @param emailRaOnRaopRoleRequestTemplate the emailRaOnRaopRoleRequestTemplate to set + */ + public void setEmailRaOnRaopRoleRequestTemplate(String emailRaOnRaopRoleRequestTemplate) { + this.emailRaOnRaopRoleRequestTemplate = emailRaOnRaopRoleRequestTemplate; + } + + /** + * @param emailUserOnRaopRoleRequestTemplate the emailUserOnRaopRoleRequestTemplate to set + */ + public void setEmailUserOnRaopRoleRequestTemplate(String emailUserOnRaopRoleRequestTemplate) { + this.emailUserOnRaopRoleRequestTemplate = emailUserOnRaopRoleRequestTemplate; + } + } diff --git a/src/main/webapp/WEB-INF/freemarker/email/emailAdminsOnRaopRoleRequestTemplate.html b/src/main/webapp/WEB-INF/freemarker/email/emailAdminsOnRaopRoleRequestTemplate.html new file mode 100644 index 0000000..c08294b --- /dev/null +++ b/src/main/webapp/WEB-INF/freemarker/email/emailAdminsOnRaopRoleRequestTemplate.html @@ -0,0 +1,25 @@ + + + + + +

    + Dear CAOP, +
    +

    RAOP Role Change Request

    +
    + A request has been raised by RAOP ${requesterCN} to change the ${requestedDN} certificate role from user to RAOP. +

    + Please review and take action on this request. +
    + If you have any questions, please contact the CA Portal admin. +
    +
    +
    + Thanks, +
    + UK CA Portal + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/freemarker/email/emailRaOnRaopRoleRequestTemplate.html b/src/main/webapp/WEB-INF/freemarker/email/emailRaOnRaopRoleRequestTemplate.html new file mode 100644 index 0000000..935190f --- /dev/null +++ b/src/main/webapp/WEB-INF/freemarker/email/emailRaOnRaopRoleRequestTemplate.html @@ -0,0 +1,26 @@ + + + + + +
    + Dear ${requesterCN}, +
    +

    Role Change Request Submitted

    +
    + Your request to change the ${requestedDN} certificate role from user to RAOP has been + successfully submitted.

    + The CAOP team has been notified and will review the request shortly.

    + You will receive a follow-up notification once the request has been approved or rejected. +
    + If you have any questions, please contact the CA Portal admin. +
    +
    +
    + Thanks, +
    + UK CA Portal + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/freemarker/email/emailUserOnRaopRoleRequestTemplate.html b/src/main/webapp/WEB-INF/freemarker/email/emailUserOnRaopRoleRequestTemplate.html new file mode 100644 index 0000000..bf1f4a2 --- /dev/null +++ b/src/main/webapp/WEB-INF/freemarker/email/emailUserOnRaopRoleRequestTemplate.html @@ -0,0 +1,26 @@ + + + + + +
    + Dear ${requestedCN}, +
    +

    Role Change Request Raised

    +
    + A request has been raised by RAOP ${requesterCN} to change your certificate role from user to + RAOP.

    + The CAOP team has been notified and will review the request shortly.

    + You will be informed once the request has been approved or rejected. +
    + If you have any questions, please contact the CA Portal admin. +
    +
    +
    + Thanks, +
    + UK CA Portal + + + + \ No newline at end of file From 0643162f94befb2cffe8363927f4b5086f657e28 Mon Sep 17 00:00:00 2001 From: Manoj Garai Date: Mon, 20 Oct 2025 17:00:51 +0100 Subject: [PATCH 5/9] Email notification for role change process --- src/main/java/uk/ac/ngs/WebConfig.java | 3 + .../uk/ac/ngs/controllers/ManageRaop.java | 22 ++++- .../java/uk/ac/ngs/controllers/RaOpHome.java | 85 +++++++++++++++- .../uk/ac/ngs/domain/RoleChangeRequest.java | 8 +- .../uk/ac/ngs/service/CertificateService.java | 16 +-- .../uk/ac/ngs/service/email/EmailService.java | 98 ++++++++++++++++++- ...mailOnRaopRoleRequestApprovalTemplate.html | 24 +++++ ...ailOnRaopRoleRequestRejectionTemplate.html | 24 +++++ .../emailOnRoleChangeToUserTemplate.html | 24 +++++ .../webapp/WEB-INF/views/raop/raophome.jsp | 3 +- 10 files changed, 291 insertions(+), 16 deletions(-) create mode 100644 src/main/webapp/WEB-INF/freemarker/email/emailOnRaopRoleRequestApprovalTemplate.html create mode 100644 src/main/webapp/WEB-INF/freemarker/email/emailOnRaopRoleRequestRejectionTemplate.html create mode 100644 src/main/webapp/WEB-INF/freemarker/email/emailOnRoleChangeToUserTemplate.html diff --git a/src/main/java/uk/ac/ngs/WebConfig.java b/src/main/java/uk/ac/ngs/WebConfig.java index 01a0c88..d9df489 100644 --- a/src/main/java/uk/ac/ngs/WebConfig.java +++ b/src/main/java/uk/ac/ngs/WebConfig.java @@ -117,6 +117,9 @@ public EmailService emailService() { emailService.setEmailAdminsOnRaopRoleRequestTemplate("emailAdminsOnRaopRoleRequestTemplate.html"); emailService.setEmailRaOnRaopRoleRequestTemplate("emailRaOnRaopRoleRequestTemplate.html"); emailService.setEmailUserOnRaopRoleRequestTemplate("emailUserOnRaopRoleRequestTemplate.html"); + emailService.setEmailOnRaopRoleRequestApprovalTemplate("emailOnRaopRoleRequestApprovalTemplate.html"); + emailService.setEmailOnRaopRoleRequestRejectionTemplate("emailOnRaopRoleRequestRejectionTemplate.html"); + emailService.setEmailOnRoleChangeToUserTemplate("emailOnRoleChangeToUserTemplate.html"); emailService.setBasePortalUrl(basePortalUrl); return emailService; } diff --git a/src/main/java/uk/ac/ngs/controllers/ManageRaop.java b/src/main/java/uk/ac/ngs/controllers/ManageRaop.java index a09320d..bb48156 100644 --- a/src/main/java/uk/ac/ngs/controllers/ManageRaop.java +++ b/src/main/java/uk/ac/ngs/controllers/ManageRaop.java @@ -32,6 +32,7 @@ import uk.ac.ngs.security.SecurityContextService; import uk.ac.ngs.service.CertUtil; import uk.ac.ngs.service.CertificateService; +import uk.ac.ngs.service.email.EmailService; import java.io.IOException; import java.util.*; @@ -49,16 +50,18 @@ public class ManageRaop { private CertificateService certificateService; private JdbcCertificateDao jdbcCertificateDao; private RoleChangeRequestRepository roleChangeRequestRepository; + private EmailService emailService; private final static Pattern DATA_PROFILE_PATTERN = Pattern.compile("PROFILE\\s?=\\s?(\\w+)$", Pattern.MULTILINE); public ManageRaop(SecurityContextService securityContextService, JdbcCertificateDao jdbcCertificateDao, CertificateService certificateService, - RoleChangeRequestRepository roleChangeRequestRepository) { + RoleChangeRequestRepository roleChangeRequestRepository, EmailService emailService) { this.securityContextService = securityContextService; this.jdbcCertificateDao = jdbcCertificateDao; this.certificateService = certificateService; this.roleChangeRequestRepository = roleChangeRequestRepository; + this.emailService = emailService; } @@ -139,7 +142,8 @@ public String changeRoleCertificate(@RequestParam long cert_key, RedirectAttribu log.info("Role change from (" + targetCert.getRole() + ") to (" + newRole + ")for certificate (" + cert_key + ") by (" + currentUser.getDn() + ")"); - message = "Role updated successfully!"; + message = "Role updated successfully!"; + sendEmailNotificationOnRoleChangeToUser(targetCert, currentUser); } else { message = "Role Change FAIL - user does not have correct permissions"; } @@ -148,6 +152,20 @@ public String changeRoleCertificate(@RequestParam long cert_key, RedirectAttribu return "redirect:/raop/manageraop"; } + private void sendEmailNotificationOnRoleChangeToUser(CertificateRow targetCert, CertificateRow currentUser) { + String actorCN = currentUser.getCn(); + String actorEmail = currentUser.getEmail(); + String targetDN = targetCert.getDn(); + String targetCN = targetCert.getCn(); + String requestedEmail = targetCert.getEmail(); + + // Send email to actor RAOP + this.emailService.sendEmailOnRoleChangeToUser(actorCN, actorCN, targetDN, actorEmail); + // Send email to user + this.emailService.sendEmailOnRoleChangeToUser(targetCN, actorCN, targetDN, requestedEmail); + } + + /** * Handle POSTs to "/raop/manageraop/requestraoprole" to request RAOP role of * certificate from User role by a RAOP. diff --git a/src/main/java/uk/ac/ngs/controllers/RaOpHome.java b/src/main/java/uk/ac/ngs/controllers/RaOpHome.java index acef35c..a53c5a7 100644 --- a/src/main/java/uk/ac/ngs/controllers/RaOpHome.java +++ b/src/main/java/uk/ac/ngs/controllers/RaOpHome.java @@ -38,6 +38,7 @@ import uk.ac.ngs.security.SecurityContextService; import uk.ac.ngs.service.CertUtil; import uk.ac.ngs.service.CertificateService; +import uk.ac.ngs.service.email.EmailService; import javax.inject.Inject; @@ -45,6 +46,7 @@ import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; //import org.springframework.security.core.GrantedAuthority; @@ -64,6 +66,8 @@ public class RaOpHome { private RoleChangeRequestRepository roleChangeRequestRepository; private JdbcCertificateDao jdbcCertificateDao; private CertificateService certificateService; + private JdbcCertificateDao jdbcCertDao; + private EmailService emailService; private final static Pattern DATA_PROFILE_PATTERN = Pattern.compile("PROFILE\\s?=\\s?(\\w+)$", Pattern.MULTILINE); @@ -125,6 +129,16 @@ public void populateModel(Model model) { if (caUser.getAuthorities() .contains(new SimpleGrantedAuthority("ROLE_CAOP"))) { List roleChangeRequests = roleChangeRequestRepository.findAll(); + Map requesterMap = roleChangeRequests.stream() + .map(RoleChangeRequest::getRequestedBy) + .distinct() // Avoid duplicate lookups + .map(jdbcCertificateDao::findById) + .filter(Objects::nonNull) + .collect(Collectors.toMap( + CertificateRow::getCert_key, + CertificateRow::getCn)); + + model.addAttribute("requesterMap", requesterMap); model.addAttribute("roleChangeRequests", roleChangeRequests); } @@ -149,6 +163,10 @@ public String approverolechange(@RequestParam long certKey, @RequestParam Intege RedirectAttributes redirectAttrs) throws IOException { String message; + CertificateRow requestedBy = roleChangeRequestRepository.findById(requestId) + .map(RoleChangeRequest::getRequestedBy) + .map(jdbcCertificateDao::findById) + .orElse(null); try { CertificateRow targetCert = jdbcCertificateDao.findById(certKey); @@ -169,6 +187,7 @@ public String approverolechange(@RequestParam long certKey, @RequestParam Intege + certKey + ") by (" + currentUser.getDn() + ")"); message = "Role updated successfully!"; + sendEmailNotificationOnApproval(targetCert, currentUser, requestedBy); } } catch (Exception e) { log.error("Error during role change approval for certKey " + certKey + " and requestId " + requestId + ": " @@ -194,13 +213,21 @@ public String approverolechange(@RequestParam long certKey, @RequestParam Intege */ @Secured("ROLE_CAOP") @RequestMapping(value = "/rejectrolechange", method = RequestMethod.POST) - public String rejectrolechange(@RequestParam Integer requestId, RedirectAttributes redirectAttrs) + public String rejectrolechange(@RequestParam long certKey, @RequestParam Integer requestId, RedirectAttributes redirectAttrs) throws IOException { String message; + CertificateRow requestedBy = roleChangeRequestRepository.findById(requestId) + .map(RoleChangeRequest::getRequestedBy) + .map(jdbcCertificateDao::findById) + .orElse(null); try { roleChangeRequestRepository.deleteById(requestId); message = "Request rejected successfully!"; + + CertificateRow targetCert = jdbcCertificateDao.findById(certKey); + CertificateRow currentUser = securityContextService.getCaUserDetails().getCertificateRow(); + sendEmailNotificationOnRejection(targetCert, currentUser, requestedBy); } catch (Exception e) { message = "Request rejection failed!"; e.printStackTrace(); @@ -247,6 +274,52 @@ private boolean canUserManageRaopRole(CertificateRow targetCert) { return isAllowed; } + private void sendEmailNotificationOnApproval(CertificateRow targetCert, CertificateRow currentUser, CertificateRow requestedBy) { + List activeCAs = this.jdbcCertDao.findActiveCAs(); + + String actorCN = currentUser.getCn(); + String requesterCN = requestedBy.getCn(); + String requesterEmail = requestedBy.getEmail(); + String targetDN = targetCert.getDn(); + String targetCN = targetCert.getCn(); + String requestedEmail = targetCert.getEmail(); + + // Send email to admins + for (CertificateRow ca : activeCAs) { + String adminEmail = ca.getEmail(); + if (!adminEmail.equalsIgnoreCase(requesterEmail)) { + this.emailService.sendEmailOnRaopRoleRequestApproval("CAOP", actorCN, targetDN, adminEmail); + } + } + // Send email to requester RAOP + this.emailService.sendEmailOnRaopRoleRequestApproval(requesterCN, actorCN, targetDN, requesterEmail); + // Send email to user + this.emailService.sendEmailOnRaopRoleRequestApproval(targetCN, actorCN, targetDN, requestedEmail); + } + + private void sendEmailNotificationOnRejection(CertificateRow targetCert, CertificateRow currentUser, CertificateRow requestedBy) { + List activeCAs = this.jdbcCertDao.findActiveCAs(); + + String actorCN = currentUser.getCn(); + String requesterCN = requestedBy.getCn(); + String requesterEmail = requestedBy.getEmail(); + String targetDN = targetCert.getDn(); + String targetCN = targetCert.getCn(); + String requestedEmail = targetCert.getEmail(); + + // Send email to admins + for (CertificateRow ca : activeCAs) { + String adminEmail = ca.getEmail(); + if (!adminEmail.equalsIgnoreCase(requesterEmail)) { + this.emailService.sendEmailOnRaopRoleRequestRejection("CAOP", actorCN, targetDN, adminEmail); + } + } + // Send email to requester RAOP + this.emailService.sendEmailOnRaopRoleRequestRejection(requesterCN, actorCN, targetDN, requesterEmail); + // Send email to user + this.emailService.sendEmailOnRaopRoleRequestRejection(targetCN, actorCN, targetDN, requestedEmail); + } + /** * Select the raop/home view to render. * @@ -292,4 +365,14 @@ public void setRoleChangeRequestRepository(JdbcCertificateDao jdbcCertificateDao public void setRoleChangeRequestRepository(CertificateService certificateService) { this.certificateService = certificateService; } + + @Inject + public void setJdbcCertificateDao(JdbcCertificateDao jdbcCertDao) { + this.jdbcCertDao = jdbcCertDao; + } + + @Inject + public void setEmailService(EmailService emailService) { + this.emailService = emailService; + } } diff --git a/src/main/java/uk/ac/ngs/domain/RoleChangeRequest.java b/src/main/java/uk/ac/ngs/domain/RoleChangeRequest.java index d4c134d..c17cca9 100644 --- a/src/main/java/uk/ac/ngs/domain/RoleChangeRequest.java +++ b/src/main/java/uk/ac/ngs/domain/RoleChangeRequest.java @@ -19,7 +19,7 @@ public class RoleChangeRequest { private String requestedRole; @Column("requested_by") - private String requestedBy; + private Long requestedBy; @Column("requested_on") private LocalDate requestedOn; @@ -27,7 +27,7 @@ public class RoleChangeRequest { // Constructors public RoleChangeRequest() {} - public RoleChangeRequest(Long certKey, String requestedRole, String requestedBy, LocalDate requestedOn) { + public RoleChangeRequest(Long certKey, String requestedRole, Long requestedBy, LocalDate requestedOn) { this.certKey = certKey; this.requestedRole = requestedRole; this.requestedBy = requestedBy; @@ -58,11 +58,11 @@ public void setRequestedRole(String requestedRole) { this.requestedRole = requestedRole; } - public String getRequestedBy() { + public Long getRequestedBy() { return requestedBy; } - public void setRequestedBy(String requestedBy) { + public void setRequestedBy(Long requestedBy) { this.requestedBy = requestedBy; } diff --git a/src/main/java/uk/ac/ngs/service/CertificateService.java b/src/main/java/uk/ac/ngs/service/CertificateService.java index a8b4535..4030532 100644 --- a/src/main/java/uk/ac/ngs/service/CertificateService.java +++ b/src/main/java/uk/ac/ngs/service/CertificateService.java @@ -214,7 +214,7 @@ private int updateCertificateRoleHelper(long cert_key, String new_cert_role) public void raiseRoleChangeRequest(long cert_key, CertificateRow targetCert, String newRole, CertificateRow currentUser) { - RoleChangeRequest roleChangeRequest = new RoleChangeRequest(cert_key, newRole, currentUser.getCn(), + RoleChangeRequest roleChangeRequest = new RoleChangeRequest(cert_key, newRole, currentUser.getCert_key(), LocalDate.now()); RoleChangeRequest savedRequest = roleChangeRequestRepository.save(roleChangeRequest); @@ -226,18 +226,20 @@ public void raiseRoleChangeRequest(long cert_key, CertificateRow targetCert, Str private void sendEmailNotification(CertificateRow targetCert, CertificateRow currentUser) { List activeCAs = this.jdbcCertDao.findActiveCAs(); - String requesterCn = currentUser.getCn(); + String requesterCN = currentUser.getCn(); String requesterEmail = currentUser.getEmail(); - String targetDn = targetCert.getDn(); - String targetCn = targetCert.getCn(); + String targetDN = targetCert.getDn(); + String targetCN = targetCert.getCn(); String requestedEmail = targetCert.getEmail(); for (CertificateRow ca : activeCAs) { String adminEmail = ca.getEmail(); - this.emailService.sendAdminsOnRaopRoleRequest(requesterCn, targetDn, adminEmail); + if (!adminEmail.equalsIgnoreCase(requesterEmail)) { + this.emailService.sendAdminsOnRaopRoleRequest(requesterCN, targetDN, adminEmail); + } } - this.emailService.sendRaOnRaopRoleRequest(requesterCn, targetDn, requesterEmail); - this.emailService.sendUserOnRaopRoleRequest(requesterCn, targetCn, requestedEmail); + this.emailService.sendRaOnRaopRoleRequest(requesterCN, targetDN, requesterEmail); + this.emailService.sendUserOnRaopRoleRequest(requesterCN, targetCN, requestedEmail); } @Inject diff --git a/src/main/java/uk/ac/ngs/service/email/EmailService.java b/src/main/java/uk/ac/ngs/service/email/EmailService.java index 00e29d1..8f879ef 100644 --- a/src/main/java/uk/ac/ngs/service/email/EmailService.java +++ b/src/main/java/uk/ac/ngs/service/email/EmailService.java @@ -52,6 +52,10 @@ public class EmailService { private String emailAdminsOnRaopRoleRequestTemplate; private String emailRaOnRaopRoleRequestTemplate; private String emailUserOnRaopRoleRequestTemplate; + private String emailOnRaopRoleRequestApprovalTemplate; + private String emailOnRaopRoleRequestRejectionTemplate; + private String emailOnRoleChangeToUserTemplate; + private String basePortalUrl; public EmailService() { @@ -435,12 +439,83 @@ public void sendUserOnRaopRoleRequest(String requesterCN, String requestedCN, St } } + /** + * Email the RAOP on user to RAOP role change request. + * + * @param recipient The CN/name of the recipient. + * @param actorCN The CN/name of the actor RAOP. + * @param targetDN the target cert DN + * @param recipientEmail + */ + public void sendEmailOnRoleChangeToUser(String recipient, String actorCN, String targetDN, String recipientEmail) { + SimpleMailMessage msg = new SimpleMailMessage(this.emailTemplate); + msg.setTo(recipientEmail); + Map vars = new HashMap<>(); + + vars.put("recipient", recipient); + vars.put("actorCN", actorCN); + vars.put("targetDN", targetDN); + + try { + this.mailSender.send(msg, vars, this.emailOnRoleChangeToUserTemplate); + } catch (MailException ex) { + log.error("MailSender " + ex.getMessage()); + } + } + + /** + * Email on user to RAOP role change request approval. + * + * @param requesterCN The CN/name of the requester. + * @param requestedDN the requested cert DN + * @param recipientEmail + */ + public void sendEmailOnRaopRoleRequestApproval(String recipient, String actorCN, String targetDN, + String recipientEmail) { + SimpleMailMessage msg = new SimpleMailMessage(this.emailTemplate); + msg.setTo(recipientEmail); + Map vars = new HashMap<>(); + + vars.put("recipient", recipient); + vars.put("actorCN", actorCN); + vars.put("targetDN", targetDN); + + try { + this.mailSender.send(msg, vars, this.emailOnRaopRoleRequestApprovalTemplate); + } catch (MailException ex) { + log.error("MailSender " + ex.getMessage()); + } + } + + /** + * Email on user to RAOP role change request rejection. + * + * @param requestorCN The CN/name of the requestor. + * @param requestedDN the requested cert DN + * @param recipientEmail + */ + public void sendEmailOnRaopRoleRequestRejection(String recipient, String requesterCN, String requestedDN, + String recipientEmail) { + SimpleMailMessage msg = new SimpleMailMessage(this.emailTemplate); + msg.setTo(recipientEmail); + Map vars = new HashMap<>(); + + vars.put("recipient", recipient); + vars.put("requesterCN", requesterCN); + vars.put("requestedDN", requestedDN); + + try { + this.mailSender.send(msg, vars, this.emailOnRaopRoleRequestRejectionTemplate); + } catch (MailException ex) { + log.error("MailSender " + ex.getMessage()); + } + } + @Inject public void setSender(Sender mailSender) { this.mailSender = mailSender; } - /** * @param emailTemplate the raRenewEmailTemplate to set */ @@ -550,4 +625,25 @@ public void setEmailUserOnRaopRoleRequestTemplate(String emailUserOnRaopRoleRequ this.emailUserOnRaopRoleRequestTemplate = emailUserOnRaopRoleRequestTemplate; } + /** + * @param emailOnRaopRoleRequestApprovalTemplate the emailOnRaopRoleRequestApprovalTemplate to set + */ + public void setEmailOnRaopRoleRequestApprovalTemplate(String emailOnRaopRoleRequestApprovalTemplate) { + this.emailOnRaopRoleRequestApprovalTemplate = emailOnRaopRoleRequestApprovalTemplate; + } + + /** + * @param emailOnRaopRoleRequestRejectionTemplate the emailOnRaopRoleRequestRejectionTemplate to set + */ + public void setEmailOnRaopRoleRequestRejectionTemplate(String emailOnRaopRoleRequestRejectionTemplate) { + this.emailOnRaopRoleRequestRejectionTemplate = emailOnRaopRoleRequestRejectionTemplate; + } + + /** + * @param emailOnRoleChangeToUserTemplate the emailOnRoleChangeToUserTemplate to set + */ + public void setEmailOnRoleChangeToUserTemplate(String emailOnRoleChangeToUserTemplate) { + this.emailOnRoleChangeToUserTemplate = emailOnRoleChangeToUserTemplate; + } + } diff --git a/src/main/webapp/WEB-INF/freemarker/email/emailOnRaopRoleRequestApprovalTemplate.html b/src/main/webapp/WEB-INF/freemarker/email/emailOnRaopRoleRequestApprovalTemplate.html new file mode 100644 index 0000000..c70099f --- /dev/null +++ b/src/main/webapp/WEB-INF/freemarker/email/emailOnRaopRoleRequestApprovalTemplate.html @@ -0,0 +1,24 @@ + + + + + +
    + Dear ${recipient}, +
    +

    RAOP Role Change Request Approved

    +
    + The request to change the ${targetDN} certificate role from user to RAOP has been approved by CAOP ${actorCN}. +

    +
    + If you have any questions, please contact the CA Portal admin. +
    +
    +
    + Thanks, +
    + UK CA Portal + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/freemarker/email/emailOnRaopRoleRequestRejectionTemplate.html b/src/main/webapp/WEB-INF/freemarker/email/emailOnRaopRoleRequestRejectionTemplate.html new file mode 100644 index 0000000..61640c5 --- /dev/null +++ b/src/main/webapp/WEB-INF/freemarker/email/emailOnRaopRoleRequestRejectionTemplate.html @@ -0,0 +1,24 @@ + + + + + +
    + Dear ${recipient}, +
    +

    RAOP Role Change Request Rejected

    +
    + The request to change the ${requestedDN} certificate role from user to RAOP has been rejected by CAOP ${requesterCN}. +

    +
    + If you have any questions, please contact the CA Portal admin. +
    +
    +
    + Thanks, +
    + UK CA Portal + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/freemarker/email/emailOnRoleChangeToUserTemplate.html b/src/main/webapp/WEB-INF/freemarker/email/emailOnRoleChangeToUserTemplate.html new file mode 100644 index 0000000..b7c75e6 --- /dev/null +++ b/src/main/webapp/WEB-INF/freemarker/email/emailOnRoleChangeToUserTemplate.html @@ -0,0 +1,24 @@ + + + + + +
    + Dear ${recipient}, +
    +

    RAOP Role has been Changed

    +
    + Role of the ${targetDN} certificate has been changed from RAOP to user by RAOP ${actorCN}. +

    +
    + If you have any questions, please contact the CA Portal admin. +
    +
    +
    + Thanks, +
    + UK CA Portal + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/views/raop/raophome.jsp b/src/main/webapp/WEB-INF/views/raop/raophome.jsp index f82fac7..d36f87f 100644 --- a/src/main/webapp/WEB-INF/views/raop/raophome.jsp +++ b/src/main/webapp/WEB-INF/views/raop/raophome.jsp @@ -126,7 +126,7 @@ href="${pageContext.request.contextPath}/raop/viewcert?certId=${row.certKey}">${row.certKey} ${row.requestedRole} - ${row.requestedBy} + ${requesterMap[row.requestedBy]} ${row.requestedOn}
    @@ -141,6 +141,7 @@ + From 6648fdde3bfb56f51159b5043fe218d4ada203c8 Mon Sep 17 00:00:00 2001 From: Manoj Garai Date: Tue, 21 Oct 2025 14:20:27 +0100 Subject: [PATCH 6/9] Address CodeQL and refactor --- .../uk/ac/ngs/controllers/ManageRaop.java | 17 ++-- .../java/uk/ac/ngs/controllers/RaOpHome.java | 18 ++-- .../uk/ac/ngs/dao/JdbcCertificateDao.java | 64 ++++++++------ .../ngs/dao/RoleChangeRequestRepository.java | 2 +- .../uk/ac/ngs/service/CertificateService.java | 13 +-- .../uk/ac/ngs/service/email/EmailService.java | 85 ++++++++++--------- .../emailAdminsOnRaopRoleRequestTemplate.html | 4 +- ...ailOnRaopRoleRequestRejectionTemplate.html | 2 +- .../emailRaOnRaopRoleRequestTemplate.html | 2 +- .../emailUserOnRaopRoleRequestTemplate.html | 2 +- 10 files changed, 111 insertions(+), 98 deletions(-) diff --git a/src/main/java/uk/ac/ngs/controllers/ManageRaop.java b/src/main/java/uk/ac/ngs/controllers/ManageRaop.java index bb48156..4d4f637 100644 --- a/src/main/java/uk/ac/ngs/controllers/ManageRaop.java +++ b/src/main/java/uk/ac/ngs/controllers/ManageRaop.java @@ -82,19 +82,14 @@ public void populateModel(Model model) { // Build RA string and add to model String ra = String.format("%s %s", ou, loc); model.addAttribute("ra", ra); - log.debug("ra is:[" + ra + "]"); + log.debug("ra is: [" + ra + "]"); // Fetch active users and RA operators for the current RA List userRaopRows = jdbcCertificateDao.findActiveUserAndRAOperatorBy(ou, o, loc); - // Exclude current user from the list - // List filteredRows = userRaopRows.stream() - // .filter(row -> !currentUserDn.equals(row.getDn())) - // .collect(Collectors.toList()); - - // log.debug("Filtered User and RA Operator rows size: " + filteredRows.size()); model.addAttribute("userRaopRows", userRaopRows); - + + // To identify pending requests List listOfRoleChangeRequestCertKey = roleChangeRequestRepository.findAll() .stream() .map(RoleChangeRequest::getCertKey) @@ -113,7 +108,7 @@ public void populateModel(Model model) { * @return raop/manageraop */ @RequestMapping(method = RequestMethod.GET) - public String manageRaop(Locale locale, Model model) { + public String manageRaop() { log.debug("Controller /raop/manageraop"); return "raop/manageraop"; } @@ -172,7 +167,7 @@ private void sendEmailNotificationOnRoleChangeToUser(CertificateRow targetCert, *

    * The view is always redirected and redirect attributes added as necessary; * - * @param cert_key key of certificate being updated + * @param cert_key key of certificate role change being requested * @param redirectAttrs * @return * @throws java.io.IOException @@ -188,7 +183,7 @@ public String requestRaopRole(@RequestParam long cert_key, RedirectAttributes re String newRole = "RA Operator"; certificateService.raiseRoleChangeRequest(cert_key, targetCert, newRole, currentUser); - log.info("Role change request from (" + targetCert.getRole() + ") to (" + newRole + ")for certificate (" + log.info("Role change request from (" + targetCert.getRole() + ") to (" + newRole + ") for certificate (" + cert_key + ") by (" + currentUser.getDn() + ") has been raised"); message = "A role change request has been raised successfully"; } else { diff --git a/src/main/java/uk/ac/ngs/controllers/RaOpHome.java b/src/main/java/uk/ac/ngs/controllers/RaOpHome.java index a53c5a7..9817830 100644 --- a/src/main/java/uk/ac/ngs/controllers/RaOpHome.java +++ b/src/main/java/uk/ac/ngs/controllers/RaOpHome.java @@ -82,9 +82,6 @@ public void populateModel(Model model) { // Get the current CaUser (so we can extract their DN) CaUser caUser = securityContextService.getCaUserDetails(); - // Collection auths = caUser.getAuthorities(); - - // model.addAttribute("caUser", caUser); // Extract the RA value from the user's certificate DN String dn = caUser.getCertificateRow().getDn(); @@ -141,7 +138,6 @@ public void populateModel(Model model) { model.addAttribute("requesterMap", requesterMap); model.addAttribute("roleChangeRequests", roleChangeRequests); } - model.addAttribute("lastPageRefreshDate", new Date()); } @@ -282,19 +278,19 @@ private void sendEmailNotificationOnApproval(CertificateRow targetCert, Certific String requesterEmail = requestedBy.getEmail(); String targetDN = targetCert.getDn(); String targetCN = targetCert.getCn(); - String requestedEmail = targetCert.getEmail(); + String targetEmail = targetCert.getEmail(); // Send email to admins for (CertificateRow ca : activeCAs) { String adminEmail = ca.getEmail(); if (!adminEmail.equalsIgnoreCase(requesterEmail)) { - this.emailService.sendEmailOnRaopRoleRequestApproval("CAOP", actorCN, targetDN, adminEmail); + this.emailService.sendEmailOnRaopRoleRequestApproval(ca.getCn() + " (CAOP)", actorCN, targetDN, adminEmail); } } // Send email to requester RAOP this.emailService.sendEmailOnRaopRoleRequestApproval(requesterCN, actorCN, targetDN, requesterEmail); // Send email to user - this.emailService.sendEmailOnRaopRoleRequestApproval(targetCN, actorCN, targetDN, requestedEmail); + this.emailService.sendEmailOnRaopRoleRequestApproval(targetCN, actorCN, targetDN, targetEmail); } private void sendEmailNotificationOnRejection(CertificateRow targetCert, CertificateRow currentUser, CertificateRow requestedBy) { @@ -305,19 +301,19 @@ private void sendEmailNotificationOnRejection(CertificateRow targetCert, Certifi String requesterEmail = requestedBy.getEmail(); String targetDN = targetCert.getDn(); String targetCN = targetCert.getCn(); - String requestedEmail = targetCert.getEmail(); + String targetEmail = targetCert.getEmail(); // Send email to admins for (CertificateRow ca : activeCAs) { String adminEmail = ca.getEmail(); if (!adminEmail.equalsIgnoreCase(requesterEmail)) { - this.emailService.sendEmailOnRaopRoleRequestRejection("CAOP", actorCN, targetDN, adminEmail); + this.emailService.sendEmailOnRaopRoleRequestRejection(ca.getCn() + " (CAOP)", actorCN, targetDN, adminEmail); } } // Send email to requester RAOP this.emailService.sendEmailOnRaopRoleRequestRejection(requesterCN, actorCN, targetDN, requesterEmail); // Send email to user - this.emailService.sendEmailOnRaopRoleRequestRejection(targetCN, actorCN, targetDN, requestedEmail); + this.emailService.sendEmailOnRaopRoleRequestRejection(targetCN, actorCN, targetDN, targetEmail); } /** @@ -326,7 +322,7 @@ private void sendEmailNotificationOnRejection(CertificateRow targetCert, Certifi * @return raop/home */ @RequestMapping(method = RequestMethod.GET) - public String raAdminHome(Locale locale, Model model) { + public String raAdminHome() { log.debug("Controller /raop/"); return "raop/raophome"; } diff --git a/src/main/java/uk/ac/ngs/dao/JdbcCertificateDao.java b/src/main/java/uk/ac/ngs/dao/JdbcCertificateDao.java index 51c81a6..6ca2e22 100644 --- a/src/main/java/uk/ac/ngs/dao/JdbcCertificateDao.java +++ b/src/main/java/uk/ac/ngs/dao/JdbcCertificateDao.java @@ -289,16 +289,25 @@ public List findActiveRAsBy(String loc, String ou) { * Find all rows with role 'CA Operator' with a * status of 'VALID' and a 'notafter' time that is in the future */ - public List findActiveCAs() { - String currentTime = getDateFormat().format(new Date()); - Map namedParameters = new HashMap<>(); - namedParameters.put("current_time", Long.parseLong(currentTime)); - - StringBuilder query = new StringBuilder(SELECT_PROJECT); - query.append("where role='CA Operator' "); - query.append("and status='VALID' and notafter > :current_time"); - return this.jdbcTemplate.query(query.toString(), namedParameters, new CertificateRowMapper()); - } + public List findActiveCAs() { + List activeCAs = Collections.emptyList(); + try { + long currentTime = Long.parseLong(getDateFormat().format(new Date())); + + Map namedParameters = new HashMap<>(); + namedParameters.put("current_time", currentTime); + + String query = SELECT_PROJECT + + "where role='CA Operator' " + + "and status='VALID' and notafter > :current_time"; + + activeCAs = this.jdbcTemplate.query(query, namedParameters, new CertificateRowMapper()); + } catch (NumberFormatException e) { + // Log the error or handle it appropriately + log.error("Failed to parse current time for CA lookup", e); + } + return activeCAs; + } /** * Find all rows with role 'RA Operator' or 'User' with a @@ -311,23 +320,30 @@ public List findActiveCAs() { * @return */ public List findActiveUserAndRAOperatorBy(String ou, String o, String loc) { - long currentTime = Long.parseLong(getDateFormat().format(new Date())); - Map namedParameters = new HashMap<>(); - namedParameters.put("current_time", currentTime); + List activeUserAndRAOperators = Collections.emptyList(); + try { + long currentTime = Long.parseLong(getDateFormat().format(new Date())); + Map namedParameters = new HashMap<>(); + namedParameters.put("current_time", currentTime); - StringBuilder query = new StringBuilder(SELECT_PROJECT) - .append("where (role = 'RA Operator' or role = 'User') "); + StringBuilder query = new StringBuilder(SELECT_PROJECT) + .append("where (role = 'RA Operator' or role = 'User') "); - String raFilter = buildRaFilter(ou, o, loc); - if (raFilter != null) { - namedParameters.put("ra", raFilter); - query.append("and dn like :ra "); + String raFilter = buildRaFilter(ou, o, loc); + if (raFilter != null) { + namedParameters.put("ra", raFilter); + query.append("and dn like :ra "); + } + query.append("and status = 'VALID' "); + query.append("and notafter > :current_time"); + + activeUserAndRAOperators = jdbcTemplate.query(query.toString(), namedParameters, + new CertificateRowMapper()); + } catch (NumberFormatException e) { + // Log the error or handle it appropriately + log.error("Failed to parse current time for user RAOP lookup", e); } - - query.append("and status = 'VALID' "); - //query.append("and notafter > :current_time"); - - return jdbcTemplate.query(query.toString(), namedParameters, new CertificateRowMapper()); + return activeUserAndRAOperators; } private String buildRaFilter(String ou, String o, String loc) { diff --git a/src/main/java/uk/ac/ngs/dao/RoleChangeRequestRepository.java b/src/main/java/uk/ac/ngs/dao/RoleChangeRequestRepository.java index 207393e..a062546 100644 --- a/src/main/java/uk/ac/ngs/dao/RoleChangeRequestRepository.java +++ b/src/main/java/uk/ac/ngs/dao/RoleChangeRequestRepository.java @@ -11,7 +11,7 @@ public interface RoleChangeRequestRepository extends CrudRepository { @Override List findAll(); - + @Override void deleteById(Integer id); } diff --git a/src/main/java/uk/ac/ngs/service/CertificateService.java b/src/main/java/uk/ac/ngs/service/CertificateService.java index 4030532..8dc7f81 100644 --- a/src/main/java/uk/ac/ngs/service/CertificateService.java +++ b/src/main/java/uk/ac/ngs/service/CertificateService.java @@ -220,26 +220,29 @@ public void raiseRoleChangeRequest(long cert_key, CertificateRow targetCert, Str RoleChangeRequest savedRequest = roleChangeRequestRepository.save(roleChangeRequest); log.debug("Role change request saved with ID: " + savedRequest.getId()); - sendEmailNotification(targetCert, currentUser); + sendEmailNotificationOnRoleChangeRequest(targetCert, currentUser); } - private void sendEmailNotification(CertificateRow targetCert, CertificateRow currentUser) { + private void sendEmailNotificationOnRoleChangeRequest(CertificateRow targetCert, CertificateRow currentUser) { List activeCAs = this.jdbcCertDao.findActiveCAs(); String requesterCN = currentUser.getCn(); String requesterEmail = currentUser.getEmail(); String targetDN = targetCert.getDn(); String targetCN = targetCert.getCn(); - String requestedEmail = targetCert.getEmail(); + String targetEmail = targetCert.getEmail(); + // Send email to CAOPs for (CertificateRow ca : activeCAs) { String adminEmail = ca.getEmail(); if (!adminEmail.equalsIgnoreCase(requesterEmail)) { - this.emailService.sendAdminsOnRaopRoleRequest(requesterCN, targetDN, adminEmail); + this.emailService.sendAdminsOnRaopRoleRequest(ca.getCn(), requesterCN, targetDN, adminEmail); } } + // Send email to RAOP this.emailService.sendRaOnRaopRoleRequest(requesterCN, targetDN, requesterEmail); - this.emailService.sendUserOnRaopRoleRequest(requesterCN, targetCN, requestedEmail); + // Send email to user + this.emailService.sendUserOnRaopRoleRequest(requesterCN, targetCN, targetEmail); } @Inject diff --git a/src/main/java/uk/ac/ngs/service/email/EmailService.java b/src/main/java/uk/ac/ngs/service/email/EmailService.java index 8f879ef..023d1c9 100644 --- a/src/main/java/uk/ac/ngs/service/email/EmailService.java +++ b/src/main/java/uk/ac/ngs/service/email/EmailService.java @@ -375,17 +375,18 @@ public void sendAdminEmailOnRoleChange(String certDn, String role, long cert_key /** * Email the CAOP on user to RAOP role change request. * - * @param requestorCN The CN/name of the requestor. - * @param requestedDN the requested cert DN + * @param requesterCN The CN/name of the requester. + * @param targetDN the target cert DN * @param recipientEmail */ - public void sendAdminsOnRaopRoleRequest(String requesterCN, String requestedDN, String recipientEmail) { + public void sendAdminsOnRaopRoleRequest(String caopCN, String requesterCN, String targetDN, String recipientEmail) { SimpleMailMessage msg = new SimpleMailMessage(this.emailTemplate); msg.setTo(recipientEmail); Map vars = new HashMap<>(); + vars.put("caopCN", caopCN); vars.put("requesterCN", requesterCN); - vars.put("requestedDN", requestedDN); + vars.put("targetDN", targetDN); vars.put("basePortalUrl", basePortalUrl); try { @@ -399,16 +400,16 @@ public void sendAdminsOnRaopRoleRequest(String requesterCN, String requestedDN, * Email the RAOP on user to RAOP role change request. * * @param requesterCN The CN/name of the requester. - * @param requestedDN the requested cert DN + * @param targetDN the target cert DN * @param recipientEmail */ - public void sendRaOnRaopRoleRequest(String requesterCN, String requestedDN, String recipientEmail) { + public void sendRaOnRaopRoleRequest(String requesterCN, String targetDN, String recipientEmail) { SimpleMailMessage msg = new SimpleMailMessage(this.emailTemplate); msg.setTo(recipientEmail); Map vars = new HashMap<>(); vars.put("requesterCN", requesterCN); - vars.put("requestedDN", requestedDN); + vars.put("targetDN", targetDN); try { this.mailSender.send(msg, vars, this.emailRaOnRaopRoleRequestTemplate); @@ -421,16 +422,16 @@ public void sendRaOnRaopRoleRequest(String requesterCN, String requestedDN, Stri * Email the user on user to RAOP role change request. * * @param requesterCN The CN/name of the requester. - * @param requestedCN the requested cert CN + * @param targetCN the target cert CN * @param recipientEmail */ - public void sendUserOnRaopRoleRequest(String requesterCN, String requestedCN, String recipientEmail) { + public void sendUserOnRaopRoleRequest(String requesterCN, String targetCN, String recipientEmail) { SimpleMailMessage msg = new SimpleMailMessage(this.emailTemplate); msg.setTo(recipientEmail); Map vars = new HashMap<>(); vars.put("requesterCN", requesterCN); - vars.put("requestedCN", requestedCN); + vars.put("targetCN", targetCN); try { this.mailSender.send(msg, vars, this.emailUserOnRaopRoleRequestTemplate); @@ -439,35 +440,12 @@ public void sendUserOnRaopRoleRequest(String requesterCN, String requestedCN, St } } - /** - * Email the RAOP on user to RAOP role change request. - * - * @param recipient The CN/name of the recipient. - * @param actorCN The CN/name of the actor RAOP. - * @param targetDN the target cert DN - * @param recipientEmail - */ - public void sendEmailOnRoleChangeToUser(String recipient, String actorCN, String targetDN, String recipientEmail) { - SimpleMailMessage msg = new SimpleMailMessage(this.emailTemplate); - msg.setTo(recipientEmail); - Map vars = new HashMap<>(); - - vars.put("recipient", recipient); - vars.put("actorCN", actorCN); - vars.put("targetDN", targetDN); - - try { - this.mailSender.send(msg, vars, this.emailOnRoleChangeToUserTemplate); - } catch (MailException ex) { - log.error("MailSender " + ex.getMessage()); - } - } - /** * Email on user to RAOP role change request approval. * - * @param requesterCN The CN/name of the requester. - * @param requestedDN the requested cert DN + * @param recipient The CN/name of the recipient + * @param actorCN The CN/name of the actor CAOP + * @param targetDN The target cert DN * @param recipientEmail */ public void sendEmailOnRaopRoleRequestApproval(String recipient, String actorCN, String targetDN, @@ -490,19 +468,20 @@ public void sendEmailOnRaopRoleRequestApproval(String recipient, String actorCN, /** * Email on user to RAOP role change request rejection. * - * @param requestorCN The CN/name of the requestor. - * @param requestedDN the requested cert DN + * @param recipient The CN/name of the recipient + * @param actorCN The CN/name of the actor CAOP + * @param targetDN the target cert DN * @param recipientEmail */ - public void sendEmailOnRaopRoleRequestRejection(String recipient, String requesterCN, String requestedDN, + public void sendEmailOnRaopRoleRequestRejection(String recipient, String actorCN, String targetDN, String recipientEmail) { SimpleMailMessage msg = new SimpleMailMessage(this.emailTemplate); msg.setTo(recipientEmail); Map vars = new HashMap<>(); vars.put("recipient", recipient); - vars.put("requesterCN", requesterCN); - vars.put("requestedDN", requestedDN); + vars.put("actorCN", actorCN); + vars.put("targetDN", targetDN); try { this.mailSender.send(msg, vars, this.emailOnRaopRoleRequestRejectionTemplate); @@ -511,6 +490,30 @@ public void sendEmailOnRaopRoleRequestRejection(String recipient, String request } } + /** + * Email on RAOP to user role change. + * + * @param recipient The CN/name of the recipient. + * @param actorCN The CN/name of the actor RAOP. + * @param targetDN the target cert DN + * @param recipientEmail + */ + public void sendEmailOnRoleChangeToUser(String recipient, String actorCN, String targetDN, String recipientEmail) { + SimpleMailMessage msg = new SimpleMailMessage(this.emailTemplate); + msg.setTo(recipientEmail); + Map vars = new HashMap<>(); + + vars.put("recipient", recipient); + vars.put("actorCN", actorCN); + vars.put("targetDN", targetDN); + + try { + this.mailSender.send(msg, vars, this.emailOnRoleChangeToUserTemplate); + } catch (MailException ex) { + log.error("MailSender " + ex.getMessage()); + } + } + @Inject public void setSender(Sender mailSender) { this.mailSender = mailSender; diff --git a/src/main/webapp/WEB-INF/freemarker/email/emailAdminsOnRaopRoleRequestTemplate.html b/src/main/webapp/WEB-INF/freemarker/email/emailAdminsOnRaopRoleRequestTemplate.html index c08294b..838f817 100644 --- a/src/main/webapp/WEB-INF/freemarker/email/emailAdminsOnRaopRoleRequestTemplate.html +++ b/src/main/webapp/WEB-INF/freemarker/email/emailAdminsOnRaopRoleRequestTemplate.html @@ -4,11 +4,11 @@

    - Dear CAOP, + Dear ${caopCN},

    RAOP Role Change Request

    - A request has been raised by RAOP ${requesterCN} to change the ${requestedDN} certificate role from user to RAOP. + A request has been raised by RAOP ${requesterCN} to change the ${targetDN} certificate role from user to RAOP.

    Please review and take action on this request.
    diff --git a/src/main/webapp/WEB-INF/freemarker/email/emailOnRaopRoleRequestRejectionTemplate.html b/src/main/webapp/WEB-INF/freemarker/email/emailOnRaopRoleRequestRejectionTemplate.html index 61640c5..2400e7d 100644 --- a/src/main/webapp/WEB-INF/freemarker/email/emailOnRaopRoleRequestRejectionTemplate.html +++ b/src/main/webapp/WEB-INF/freemarker/email/emailOnRaopRoleRequestRejectionTemplate.html @@ -8,7 +8,7 @@

    RAOP Role Change Request Rejected

    - The request to change the ${requestedDN} certificate role from user to RAOP has been rejected by CAOP ${requesterCN}. + The request to change the ${targetDN} certificate role from user to RAOP has been rejected by CAOP ${actorCN}.

    If you have any questions, please contact the CA Portal admin. diff --git a/src/main/webapp/WEB-INF/freemarker/email/emailRaOnRaopRoleRequestTemplate.html b/src/main/webapp/WEB-INF/freemarker/email/emailRaOnRaopRoleRequestTemplate.html index 935190f..ac10fbc 100644 --- a/src/main/webapp/WEB-INF/freemarker/email/emailRaOnRaopRoleRequestTemplate.html +++ b/src/main/webapp/WEB-INF/freemarker/email/emailRaOnRaopRoleRequestTemplate.html @@ -8,7 +8,7 @@

    Role Change Request Submitted

    - Your request to change the ${requestedDN} certificate role from user to RAOP has been + Your request to change the ${targetDN} certificate role from user to RAOP has been successfully submitted.

    The CAOP team has been notified and will review the request shortly.

    You will receive a follow-up notification once the request has been approved or rejected. diff --git a/src/main/webapp/WEB-INF/freemarker/email/emailUserOnRaopRoleRequestTemplate.html b/src/main/webapp/WEB-INF/freemarker/email/emailUserOnRaopRoleRequestTemplate.html index bf1f4a2..211078a 100644 --- a/src/main/webapp/WEB-INF/freemarker/email/emailUserOnRaopRoleRequestTemplate.html +++ b/src/main/webapp/WEB-INF/freemarker/email/emailUserOnRaopRoleRequestTemplate.html @@ -4,7 +4,7 @@
    - Dear ${requestedCN}, + Dear ${targetCN},

    Role Change Request Raised

    From 180e9ee0542dc543ca867c9630d6032a33d86042 Mon Sep 17 00:00:00 2001 From: Manoj Garai Date: Tue, 21 Oct 2025 16:27:52 +0100 Subject: [PATCH 7/9] Add DDL for role_change_request --- usefulStuff/sql/create_openca_ddl.sql | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/usefulStuff/sql/create_openca_ddl.sql b/usefulStuff/sql/create_openca_ddl.sql index cab97e1..0cd4bc8 100644 --- a/usefulStuff/sql/create_openca_ddl.sql +++ b/usefulStuff/sql/create_openca_ddl.sql @@ -312,6 +312,21 @@ CREATE TABLE raoplist ( ); +-- +-- Name: role_change_request; Type: TABLE; Schema: public; Owner: -; Tablespace: +-- + +CREATE TABLE role_change_request ( + id serial4 NOT NULL, + cert_key bigint NULL, + requested_role text NULL, + requested_by bigint NULL, + requested_on date NULL, + CONSTRAINT role_change_request_pk PRIMARY KEY (id), + CONSTRAINT role_change_request_unique UNIQUE (cert_key) +); + + -- -- Name: seq_bulk; Type: SEQUENCE; Schema: public; Owner: - -- From 2465171b8b09204e3f35baff6c9cf4a5388c628b Mon Sep 17 00:00:00 2001 From: Manoj Garai Date: Fri, 31 Oct 2025 14:12:03 +0000 Subject: [PATCH 8/9] Remove unused code --- .../java/uk/ac/ngs/controllers/RaOpHome.java | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/main/java/uk/ac/ngs/controllers/RaOpHome.java b/src/main/java/uk/ac/ngs/controllers/RaOpHome.java index 9817830..ea3107c 100644 --- a/src/main/java/uk/ac/ngs/controllers/RaOpHome.java +++ b/src/main/java/uk/ac/ngs/controllers/RaOpHome.java @@ -64,7 +64,6 @@ public class RaOpHome { private JdbcRequestDao jdbcRequestDao; private JdbcCrrDao jdbcCrrDao; private RoleChangeRequestRepository roleChangeRequestRepository; - private JdbcCertificateDao jdbcCertificateDao; private CertificateService certificateService; private JdbcCertificateDao jdbcCertDao; private EmailService emailService; @@ -129,7 +128,7 @@ public void populateModel(Model model) { Map requesterMap = roleChangeRequests.stream() .map(RoleChangeRequest::getRequestedBy) .distinct() // Avoid duplicate lookups - .map(jdbcCertificateDao::findById) + .map(jdbcCertDao::findById) .filter(Objects::nonNull) .collect(Collectors.toMap( CertificateRow::getCert_key, @@ -161,11 +160,11 @@ public String approverolechange(@RequestParam long certKey, @RequestParam Intege String message; CertificateRow requestedBy = roleChangeRequestRepository.findById(requestId) .map(RoleChangeRequest::getRequestedBy) - .map(jdbcCertificateDao::findById) + .map(jdbcCertDao::findById) .orElse(null); try { - CertificateRow targetCert = jdbcCertificateDao.findById(certKey); + CertificateRow targetCert = jdbcCertDao.findById(certKey); CertificateRow currentUser = securityContextService.getCaUserDetails().getCertificateRow(); if (targetCert == null) { @@ -214,14 +213,14 @@ public String rejectrolechange(@RequestParam long certKey, @RequestParam Integer String message; CertificateRow requestedBy = roleChangeRequestRepository.findById(requestId) .map(RoleChangeRequest::getRequestedBy) - .map(jdbcCertificateDao::findById) + .map(jdbcCertDao::findById) .orElse(null); try { roleChangeRequestRepository.deleteById(requestId); message = "Request rejected successfully!"; - CertificateRow targetCert = jdbcCertificateDao.findById(certKey); + CertificateRow targetCert = jdbcCertDao.findById(certKey); CertificateRow currentUser = securityContextService.getCaUserDetails().getCertificateRow(); sendEmailNotificationOnRejection(targetCert, currentUser, requestedBy); } catch (Exception e) { @@ -353,12 +352,7 @@ public void setRoleChangeRequestRepository(RoleChangeRequestRepository roleChang } @Inject - public void setRoleChangeRequestRepository(JdbcCertificateDao jdbcCertificateDao) { - this.jdbcCertificateDao = jdbcCertificateDao; - } - - @Inject - public void setRoleChangeRequestRepository(CertificateService certificateService) { + public void setCertificateService(CertificateService certificateService) { this.certificateService = certificateService; } From 65459d8e91936feb90f0202941456218b3c7e6d2 Mon Sep 17 00:00:00 2001 From: garaimanoj <99975605+garaimanoj@users.noreply.github.com> Date: Thu, 13 Nov 2025 15:27:24 +0000 Subject: [PATCH 9/9] Gt1150 test cases (#2) * Add test cases for controllers * Update maven.yml with SUPPORTED_PKCS10_MIN_MODULUS * Update maven.yml * Update maven.yml * Add application.properties to run test * Fix test * Remove unused import * Revert the user CN change --- .../java/uk/ac/ngs/dao/JdbcBulk_ChainDao.java | 14 +- .../java/uk/ac/ngs/dao/JdbcCaUserAuthDao.java | 15 +- .../uk/ac/ngs/dao/JdbcCertificateDao.java | 21 +- src/main/java/uk/ac/ngs/dao/JdbcCrrDao.java | 13 +- .../java/uk/ac/ngs/dao/JdbcRalistDao.java | 10 +- .../java/uk/ac/ngs/dao/JdbcRaopListDao.java | 10 +- .../java/uk/ac/ngs/dao/JdbcRequestDao.java | 13 +- .../ac/ngs/controllers/ManageRaopTests.java | 197 ++++++++++++++ .../uk/ac/ngs/controllers/RaopHomeTests.java | 245 ++++++++++++++++++ src/test/java/uk/ac/ngs/dao/GeneralTests.java | 12 +- .../uk/ac/ngs/dao/JdbcCertificateDaoTest.java | 84 +++++- .../ngs/service/CertificateServiceTests.java | 52 +++- src/test/resources/application.properties | 53 ++++ 13 files changed, 648 insertions(+), 91 deletions(-) create mode 100644 src/test/java/uk/ac/ngs/controllers/ManageRaopTests.java create mode 100644 src/test/java/uk/ac/ngs/controllers/RaopHomeTests.java create mode 100644 src/test/resources/application.properties diff --git a/src/main/java/uk/ac/ngs/dao/JdbcBulk_ChainDao.java b/src/main/java/uk/ac/ngs/dao/JdbcBulk_ChainDao.java index 48d9618..be4e34e 100644 --- a/src/main/java/uk/ac/ngs/dao/JdbcBulk_ChainDao.java +++ b/src/main/java/uk/ac/ngs/dao/JdbcBulk_ChainDao.java @@ -40,7 +40,8 @@ public class JdbcBulk_ChainDao { private static final Log log = LogFactory.getLog(JdbcBulk_ChainDao.class); - public JdbcBulk_ChainDao() { + public JdbcBulk_ChainDao(NamedParameterJdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; } /** @@ -102,15 +103,4 @@ public Long getCreateNewIdForOldId(long oldId) { } return newBulkId; } - - - /** - * Set the JDBC dataSource. - * - * @param dataSource - */ - @Autowired - public void setDataSource(DataSource dataSource) { - this.jdbcTemplate = new NamedParameterJdbcTemplate(dataSource); - } } diff --git a/src/main/java/uk/ac/ngs/dao/JdbcCaUserAuthDao.java b/src/main/java/uk/ac/ngs/dao/JdbcCaUserAuthDao.java index 5a27a3c..eaf7335 100644 --- a/src/main/java/uk/ac/ngs/dao/JdbcCaUserAuthDao.java +++ b/src/main/java/uk/ac/ngs/dao/JdbcCaUserAuthDao.java @@ -38,21 +38,10 @@ public class JdbcCaUserAuthDao { private NamedParameterJdbcTemplate jdbcTemplate; - public JdbcCaUserAuthDao() { - + public JdbcCaUserAuthDao(NamedParameterJdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; } - /** - * Set the JDBC dataSource. - * - * @param dataSource - */ - @Autowired - public void setDataSource(DataSource dataSource) { - this.jdbcTemplate = new NamedParameterJdbcTemplate(dataSource); - } - - private static final class CertificateRowMapper implements RowMapper { public CertificateRow mapRow(ResultSet rs, int rowNum) throws SQLException { CertificateRow cr = new CertificateRow(); diff --git a/src/main/java/uk/ac/ngs/dao/JdbcCertificateDao.java b/src/main/java/uk/ac/ngs/dao/JdbcCertificateDao.java index 6ca2e22..90f14c5 100644 --- a/src/main/java/uk/ac/ngs/dao/JdbcCertificateDao.java +++ b/src/main/java/uk/ac/ngs/dao/JdbcCertificateDao.java @@ -14,7 +14,6 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; @@ -22,7 +21,6 @@ import uk.ac.ngs.common.Pair; import uk.ac.ngs.domain.CertificateRow; -import javax.sql.DataSource; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.UnsupportedEncodingException; @@ -85,12 +83,11 @@ public class JdbcCertificateDao { * only EMAIL_EQ is used to create the query. */ public enum WHERE_PARAMS { - DN_HAS_RA_LIKE, CN_LIKE, EMAIL_LIKE, EMAIL_EQ, DN_LIKE, ROLE_LIKE, STATUS_LIKE, DATA_LIKE, NOTAFTER_GREATERTHAN_CURRENTTIME } - public JdbcCertificateDao() { - + public JdbcCertificateDao(NamedParameterJdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; } private static DateFormat getDateFormat() { @@ -99,16 +96,6 @@ private static DateFormat getDateFormat() { return utcTimeStampFormatter; } - /** - * Set the JDBC dataSource. - * - * @param dataSource - */ - @Autowired - public void setDataSource(DataSource dataSource) { - this.jdbcTemplate = new NamedParameterJdbcTemplate(dataSource); - } - private static final class CertificateRowMapper implements RowMapper { public CertificateRow mapRow(ResultSet rs, int rowNum) throws SQLException { @@ -298,8 +285,8 @@ public List findActiveCAs() { namedParameters.put("current_time", currentTime); String query = SELECT_PROJECT + - "where role='CA Operator' " + - "and status='VALID' and notafter > :current_time"; + "where role = 'CA Operator' " + + "and status = 'VALID' and notafter > :current_time"; activeCAs = this.jdbcTemplate.query(query, namedParameters, new CertificateRowMapper()); } catch (NumberFormatException e) { diff --git a/src/main/java/uk/ac/ngs/dao/JdbcCrrDao.java b/src/main/java/uk/ac/ngs/dao/JdbcCrrDao.java index 05e0eac..85f6d1c 100644 --- a/src/main/java/uk/ac/ngs/dao/JdbcCrrDao.java +++ b/src/main/java/uk/ac/ngs/dao/JdbcCrrDao.java @@ -61,7 +61,8 @@ public class JdbcCrrDao { private final DateFormat dataNotBeforeDateFormat2 = new SimpleDateFormat("E MMM dd HH:mm:ss zzz yyyy"); - public JdbcCrrDao() { + public JdbcCrrDao(NamedParameterJdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; this.utcDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); } @@ -78,16 +79,6 @@ public enum WHERE_PARAMS { DN_HAS_RA_LIKE, RA_EQ, STATUS_EQ, DN_LIKE, DATA_LIKE, CN_LIKE } - /** - * Set the JDBC dataSource. - * - * @param dataSource - */ - @Autowired - public void setDataSource(DataSource dataSource) { - this.jdbcTemplate = new NamedParameterJdbcTemplate(dataSource); - } - private final static class CrrRowMapper implements RowMapper { public CrrRow mapRow(ResultSet rs, int rowNum) throws SQLException { diff --git a/src/main/java/uk/ac/ngs/dao/JdbcRalistDao.java b/src/main/java/uk/ac/ngs/dao/JdbcRalistDao.java index 7d37f7c..5ac82e0 100644 --- a/src/main/java/uk/ac/ngs/dao/JdbcRalistDao.java +++ b/src/main/java/uk/ac/ngs/dao/JdbcRalistDao.java @@ -43,14 +43,8 @@ public class JdbcRalistDao { private static final String ORDER_BY = "order by order_id "; public static final String SQL_SELECT_ALL = "select ra_id, order_id, ou, l, active from ralist "; - /** - * Set the JDBC dataSource. - * - * @param dataSource - */ - @Autowired - public void setDataSource(DataSource dataSource) { - this.jdbcTemplate = new NamedParameterJdbcTemplate(dataSource); + public JdbcRalistDao(NamedParameterJdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; } private static final class RalistRowMapper implements RowMapper { diff --git a/src/main/java/uk/ac/ngs/dao/JdbcRaopListDao.java b/src/main/java/uk/ac/ngs/dao/JdbcRaopListDao.java index 508d753..d364ae1 100644 --- a/src/main/java/uk/ac/ngs/dao/JdbcRaopListDao.java +++ b/src/main/java/uk/ac/ngs/dao/JdbcRaopListDao.java @@ -45,14 +45,8 @@ public class JdbcRaopListDao { + "title, conemail, location, ra_id, department_hp, " + "institute_hp, active, ra_id2 from raoplist"; - /** - * Set the JDBC dataSource. - * - * @param dataSource - */ - @Autowired - public void setDataSource(DataSource dataSource) { - this.jdbcTemplate = new NamedParameterJdbcTemplate(dataSource); + public JdbcRaopListDao(NamedParameterJdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; } diff --git a/src/main/java/uk/ac/ngs/dao/JdbcRequestDao.java b/src/main/java/uk/ac/ngs/dao/JdbcRequestDao.java index f192fdd..7d41170 100644 --- a/src/main/java/uk/ac/ngs/dao/JdbcRequestDao.java +++ b/src/main/java/uk/ac/ngs/dao/JdbcRequestDao.java @@ -69,7 +69,8 @@ public class JdbcRequestDao { private final DateFormat dataNotBeforeDateFormat2 = new SimpleDateFormat("E MMM dd HH:mm:ss zzz yyyy"); - public JdbcRequestDao() { + public JdbcRequestDao(NamedParameterJdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; this.utcDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); } @@ -100,16 +101,6 @@ private static DateFormat getDateFormat() { return utcTimeStampFormatter; } - /** - * Set the JDBC dataSource. - * - * @param dataSource - */ - @Autowired - public void setDataSource(DataSource dataSource) { - this.jdbcTemplate = new NamedParameterJdbcTemplate(dataSource); - } - private static class RequestRowMapper implements RowMapper { public RequestRow mapRow(ResultSet rs, int rowNum) throws SQLException { diff --git a/src/test/java/uk/ac/ngs/controllers/ManageRaopTests.java b/src/test/java/uk/ac/ngs/controllers/ManageRaopTests.java new file mode 100644 index 0000000..0d55d35 --- /dev/null +++ b/src/test/java/uk/ac/ngs/controllers/ManageRaopTests.java @@ -0,0 +1,197 @@ +package uk.ac.ngs.controllers; + +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.util.Arrays; + +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; + +import org.mockito.Mock; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; + +import uk.ac.ngs.SecurityConfiguration; +import uk.ac.ngs.dao.JdbcCaUserAuthDao; +import uk.ac.ngs.dao.JdbcCertificateDao; +import uk.ac.ngs.dao.JdbcRalistDao; +import uk.ac.ngs.dao.JdbcRequestDao; +import uk.ac.ngs.dao.RoleChangeRequestRepository; +import uk.ac.ngs.domain.CertificateRow; +import uk.ac.ngs.security.CaUser; +import uk.ac.ngs.security.SecurityContextService; +import uk.ac.ngs.service.CertificateService; +import uk.ac.ngs.service.email.EmailService; + +import org.junit.jupiter.api.Test; + +@WebMvcTest(ManageRaop.class) +@Import(SecurityConfiguration.class) +public class ManageRaopTests { + @Autowired + private MockMvc mockMvc; + + @MockBean + private CaUser caUser; + + @MockBean + private JdbcRalistDao ralistDao; + + @MockBean + private JdbcRequestDao requestDao; + + @MockBean + private JdbcCaUserAuthDao caUserAuthDao; + + @MockBean + private JdbcCertificateDao jdbcCertificateDao; + + @MockBean + private CertificateService certificateService; + + @MockBean + private SecurityContextService securityContextService; + + @MockBean + private RoleChangeRequestRepository roleChangeRequestRepository; + + @MockBean + private EmailService emailService; + + @MockBean + private RedirectAttributes redirectAttributes; + + @Mock + ManageRaop manageRaopController; + + @Test + @WithMockUser(roles = "RAOP") + public void testChangeRoleToUser_successfulRoleChange() throws Exception { + long certKey = 123L; + CertificateRow targetCert = getTargetCert(); + + CertificateRow currentUser = new CertificateRow(); + currentUser.setDn("CN=Test User,OU=xyz_c,O=stfc.com,L=OX,ST=OX,C=UK"); + + when(jdbcCertificateDao.findById(certKey)).thenReturn(targetCert); + when(securityContextService.getCaUserDetails()).thenReturn(caUser); + when(caUser.getAuthorities()).thenReturn(Arrays.asList(new SimpleGrantedAuthority("ROLE_RAOP"))); + when(caUser.getCertificateRow()).thenReturn(currentUser); + + mockMvc.perform(post("/raop/manageraop/changeroletouser") + .with(csrf()) + .param("cert_key", String.valueOf(certKey))) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/raop/manageraop")) + .andExpect(flash().attribute("responseMessage", "Role updated successfully!")); + + verify(certificateService).updateCertificateRole(certKey, "User"); + } + + private CertificateRow getTargetCert() { + CertificateRow targetCert = new CertificateRow(); + targetCert.setDn("CN=Target User,OU=xyz_c,O=stfc.com,L=OX,ST=OX,C=UK"); + targetCert.setRole("RA Operator"); + targetCert.setData("ROLE=RA Operator\r\nPROFILE=UKPERSON"); + return targetCert; + } + + @Test + @WithMockUser(roles = "RAOP") + public void testChangeRoleToUser_permissionDenied() throws Exception { + long certKey = 123L; + CertificateRow targetCert = new CertificateRow(); + targetCert.setRole("RA Operator"); + + CertificateRow currentUser = new CertificateRow(); + currentUser.setDn("CN=Test User"); + + when(jdbcCertificateDao.findById(certKey)).thenReturn(targetCert); + when(securityContextService.getCaUserDetails()).thenReturn(caUser); + when(caUser.getCertificateRow()).thenReturn(currentUser); + + mockMvc.perform(post("/raop/manageraop/changeroletouser") + .with(csrf()) + .param("cert_key", String.valueOf(certKey))) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/raop/manageraop")) + // Role Change FAIL as caUser.getAuthorities() not mocked and does not have RAOP role + .andExpect(flash().attribute("responseMessage", "Role Change FAIL - user does not have correct permissions")); + + verify(certificateService, never()).updateCertificateRole(anyLong(), anyString()); + } + + @Test + @WithMockUser(roles = "USER") + public void testChangeRoleToUser_permissionDeniedForUser() throws Exception { + long certKey = 123L; + CertificateRow targetCert = new CertificateRow(); + targetCert.setRole("RA Operator"); + + CertificateRow currentUser = new CertificateRow(); + currentUser.setDn("CN=Test User"); + + when(jdbcCertificateDao.findById(certKey)).thenReturn(targetCert); + when(securityContextService.getCaUserDetails()).thenReturn(caUser); + when(caUser.getCertificateRow()).thenReturn(currentUser); + + mockMvc.perform(post("/raop/manageraop/changeroletouser") + .with(csrf()) + .param("cert_key", String.valueOf(certKey))) + .andExpect(status().isOk()) + .andExpect(forwardedUrl("/WEB-INF/views//denied/denied.jsp")) + .andExpect(result -> { + Throwable resolvedException = result.getResolvedException(); + assert resolvedException instanceof AccessDeniedException; + }); + + verify(certificateService, never()).updateCertificateRole(anyLong(), anyString()); + } + + @Test + @WithMockUser(roles = "RAOP") + void testAccessWithRAOPRole_shouldSucceed() throws Exception { + long certKey = 123L; + + CertificateRow targetCert = new CertificateRow(); + targetCert.setRole("RA Operator"); + + CertificateRow currentUser = new CertificateRow(); + currentUser.setDn("CN=Test User"); + + when(jdbcCertificateDao.findById(certKey)).thenReturn(targetCert); + when(securityContextService.getCaUserDetails()).thenReturn(caUser); + when(caUser.getCertificateRow()).thenReturn(currentUser); + + mockMvc.perform(get("/raop/manageraop")) + .andExpect(status().isOk()); + } + + @Test + @WithMockUser(roles = "USER") + void testAccessWithUserRole_shouldBeDenied() throws Exception { + + mockMvc.perform(get("/raop/manageraop")) + .andExpect(status().isOk()) + .andExpect(forwardedUrl("/WEB-INF/views//denied/denied.jsp")) + .andExpect(result -> { + Throwable resolvedException = result.getResolvedException(); + assert resolvedException instanceof AccessDeniedException; + }); + + } +} diff --git a/src/test/java/uk/ac/ngs/controllers/RaopHomeTests.java b/src/test/java/uk/ac/ngs/controllers/RaopHomeTests.java new file mode 100644 index 0000000..e4c4b07 --- /dev/null +++ b/src/test/java/uk/ac/ngs/controllers/RaopHomeTests.java @@ -0,0 +1,245 @@ +package uk.ac.ngs.controllers; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.flash; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.Arrays; +import java.util.Optional; +import java.util.concurrent.ThreadLocalRandom; + +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; + +import uk.ac.ngs.SecurityConfiguration; +import uk.ac.ngs.dao.JdbcCaUserAuthDao; +import uk.ac.ngs.dao.JdbcCertificateDao; +import uk.ac.ngs.dao.JdbcCrrDao; +import uk.ac.ngs.dao.JdbcRalistDao; +import uk.ac.ngs.dao.JdbcRaopListDao; +import uk.ac.ngs.dao.JdbcRequestDao; +import uk.ac.ngs.dao.RoleChangeRequestRepository; +import uk.ac.ngs.domain.CertificateRow; +import uk.ac.ngs.domain.RoleChangeRequest; +import uk.ac.ngs.security.CaUser; +import uk.ac.ngs.security.SecurityContextService; +import uk.ac.ngs.service.CertificateService; +import uk.ac.ngs.service.email.EmailService; + +@WebMvcTest(RaOpHome.class) +@Import(SecurityConfiguration.class) +public class RaopHomeTests { + @Autowired + private MockMvc mockMvc; + + @MockBean + private CaUser caUser; + + @MockBean + private JdbcCertificateDao jdbcCertificateDao; + + @MockBean + private JdbcRalistDao ralistDao; + + @MockBean + private JdbcRaopListDao jdbcRaopListDao; + + @MockBean + private JdbcCrrDao jdbcCrrDao; + + @MockBean + private JdbcRequestDao requestDao; + + @MockBean + private JdbcCaUserAuthDao caUserAuthDao; + + @MockBean + private CertificateService certificateService; + + @MockBean + private SecurityContextService securityContextService; + + @MockBean + private RoleChangeRequestRepository roleChangeRequestRepository; + + @MockBean + private EmailService emailService; + + @MockBean + private RedirectAttributes redirectAttributes; + + @Mock + RaOpHome raOpHomeController; + + @Test + @WithMockUser(roles = { "RAOP", "CAOP" }) + public void testApproveRoleChange_successfulRoleChange() throws Exception { + long certKey = 123L; + int requestId = 555; + CertificateRow targetCert = getCert("Target User", "User"); + CertificateRow requesterCert = getCert("Requester", "RA Operator"); + RoleChangeRequest roleChangeRequest = new RoleChangeRequest(); + roleChangeRequest.setId(requestId); + roleChangeRequest.setRequestedBy(requesterCert.getCert_key()); + + CertificateRow currentUser = getCert("CAOP User", "CA Operator"); + + when(jdbcCertificateDao.findById(certKey)).thenReturn(targetCert); + when(roleChangeRequestRepository.findById(roleChangeRequest.getId())) + .thenReturn(Optional.of(roleChangeRequest)); + when(jdbcCertificateDao.findById(requesterCert.getCert_key())).thenReturn(requesterCert); + when(jdbcCertificateDao.findActiveCAs()).thenReturn(Arrays.asList(currentUser)); + + when(securityContextService.getCaUserDetails()).thenReturn(caUser); + when(caUser.getAuthorities()).thenReturn(Arrays.asList(new SimpleGrantedAuthority("ROLE_CAOP"), + new SimpleGrantedAuthority("ROLE_RAOP"))); + when(caUser.getCertificateRow()).thenReturn(currentUser); + + mockMvc.perform(post("/raop/approverolechange") + .with(csrf()) + .param("certKey", String.valueOf(certKey)) + .param("requestId", String.valueOf(requestId))) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/raop")) + .andExpect(flash().attribute("responseMessage", "Role updated successfully!")); + + verify(certificateService).updateCertificateRole(certKey, "RA Operator"); + verify(emailService, times(3)).sendEmailOnRaopRoleRequestApproval(any(), any(), any(), any()); + } + + private CertificateRow getCert(String userName, String role) { + int certKey = ThreadLocalRandom.current().nextInt(100, 1000); + CertificateRow cert = new CertificateRow(); + cert.setCert_key(certKey); + cert.setDn("CN=" + userName + ",OU=xyz_c,O=stfc.com,L=OX,ST=OX,C=UK"); + cert.setEmail("user" + certKey + "@example.com"); + cert.setRole(role); + cert.setData("ROLE=" + role + "\r\nPROFILE=UKPERSON"); + return cert; + } + + @Test + @WithMockUser(roles = "RAOP") + public void testApproveRoleChange_permissionDeniedForRAOP() throws Exception { + performPermissionDeniedTest("RAOP User", "RA Operator"); + } + + @Test + @WithMockUser(roles = "USER") + public void testApproveRoleChange_permissionDeniedForUser() throws Exception { + performPermissionDeniedTest("Test User", "User"); + } + + private void performPermissionDeniedTest(String userName, String userRole) throws Exception { + long certKey = 123L; + int requestId = 555; + CertificateRow currentUser = getCert(userName, userRole); + + when(securityContextService.getCaUserDetails()).thenReturn(caUser); + when(caUser.getCertificateRow()).thenReturn(currentUser); + + mockMvc.perform(post("/raop/approverolechange") + .with(csrf()) + .param("certKey", String.valueOf(certKey)) + .param("requestId", String.valueOf(requestId))) + .andExpect(status().isOk()) + .andExpect(forwardedUrl("/WEB-INF/views//denied/denied.jsp")) + .andExpect(result -> { + Throwable resolvedException = result.getResolvedException(); + assert resolvedException instanceof AccessDeniedException; + }); + + verify(certificateService, never()).updateCertificateRole(anyLong(), anyString()); + verify(emailService, never()).sendEmailOnRaopRoleRequestApproval(any(), any(), any(), any()); + } + + @Test + @WithMockUser(roles = { "RAOP", "CAOP" }) + public void testRejectRoleChange_successful() throws Exception { + long certKey = 123L; + int requestId = 555; + CertificateRow targetCert = getCert("Target User", "User"); + CertificateRow requesterCert = getCert("Requester", "RA Operator"); + RoleChangeRequest roleChangeRequest = new RoleChangeRequest(); + roleChangeRequest.setId(requestId); + roleChangeRequest.setRequestedBy(requesterCert.getCert_key()); + + CertificateRow currentUser = getCert("CAOP User", "CA Operator"); + + when(jdbcCertificateDao.findById(certKey)).thenReturn(targetCert); + when(roleChangeRequestRepository.findById(roleChangeRequest.getId())) + .thenReturn(Optional.of(roleChangeRequest)); + when(jdbcCertificateDao.findById(requesterCert.getCert_key())).thenReturn(requesterCert); + when(jdbcCertificateDao.findActiveCAs()).thenReturn(Arrays.asList(currentUser)); + + when(securityContextService.getCaUserDetails()).thenReturn(caUser); + when(caUser.getAuthorities()).thenReturn(Arrays.asList(new SimpleGrantedAuthority("ROLE_CAOP"), + new SimpleGrantedAuthority("ROLE_RAOP"))); + when(caUser.getCertificateRow()).thenReturn(currentUser); + + mockMvc.perform(post("/raop/rejectrolechange") + .with(csrf()) + .param("certKey", String.valueOf(certKey)) + .param("requestId", String.valueOf(requestId))) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/raop")) + .andExpect(flash().attribute("responseMessage", "Request rejected successfully!")); + + verify(roleChangeRequestRepository).deleteById(requestId); + verify(emailService, times(3)).sendEmailOnRaopRoleRequestRejection(any(), any(), any(), any()); + } + + @Test + @WithMockUser(roles = "RAOP") + public void testRejectRoleChange_permissionDeniedForRAOP() throws Exception { + performPermissionDeniedTestForRejectRoleChange("RAOP User", "RA Operator"); + } + + @Test + @WithMockUser(roles = "USER") + public void testRejectRoleChange_permissionDeniedForUser() throws Exception { + performPermissionDeniedTestForRejectRoleChange("Test User", "User"); + } + + private void performPermissionDeniedTestForRejectRoleChange(String userName, String userRole) throws Exception { + long certKey = 123L; + int requestId = 555; + CertificateRow currentUser = getCert(userName, userRole); + + when(securityContextService.getCaUserDetails()).thenReturn(caUser); + when(caUser.getCertificateRow()).thenReturn(currentUser); + + mockMvc.perform(post("/raop/rejectrolechange") + .with(csrf()) + .param("certKey", String.valueOf(certKey)) + .param("requestId", String.valueOf(requestId))) + .andExpect(status().isOk()) + .andExpect(forwardedUrl("/WEB-INF/views//denied/denied.jsp")) + .andExpect(result -> { + Throwable resolvedException = result.getResolvedException(); + assert resolvedException instanceof AccessDeniedException; + }); + + verify(roleChangeRequestRepository, never()).deleteById(requestId); + verify(emailService, never()).sendEmailOnRaopRoleRequestRejection(any(), any(), any(), any()); + } +} diff --git a/src/test/java/uk/ac/ngs/dao/GeneralTests.java b/src/test/java/uk/ac/ngs/dao/GeneralTests.java index 7478cbb..499d22d 100644 --- a/src/test/java/uk/ac/ngs/dao/GeneralTests.java +++ b/src/test/java/uk/ac/ngs/dao/GeneralTests.java @@ -23,6 +23,9 @@ import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.mockito.Mock; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; + import static org.junit.Assert.*; import uk.ac.ngs.service.CrrManagerService; @@ -33,6 +36,9 @@ */ public class GeneralTests { + @Mock + private NamedParameterJdbcTemplate jdbcTemplate; + public GeneralTests() { } @@ -64,7 +70,7 @@ public void testCNformats(){ assertFalse(userCN_Pattern.matcher("DAVE - DAVE +").matches()); assertFalse(userCN_Pattern.matcher("DAVE - DAVE /").matches()); assertFalse(userCN_Pattern.matcher("' adfa ").matches()); - assertFalse(userCN_Pattern.matcher("davídó garçoné").matches()); + assertFalse(userCN_Pattern.matcher("davídó garçoné").matches()); // Note, this method uses 'find()' to find an illegal char using negation userCN_Pattern = Pattern.compile("[^A-Za-z0-9\\-\\(\\) ]"); @@ -76,7 +82,7 @@ public void testCNformats(){ assertTrue(userCN_Pattern.matcher("DAVE test the - (3rd) +").find()); assertTrue(userCN_Pattern.matcher("DAVE - DAVE /").find()); assertTrue(userCN_Pattern.matcher("' adfa ").find()); - assertTrue(userCN_Pattern.matcher("davídó garçoné").find()); + assertTrue(userCN_Pattern.matcher("davídó garçoné").find()); // Note, illegal multiple white space/tabs have to be tested separtely assertFalse(" hello world ".contains(" ")); // single space is ok @@ -221,7 +227,7 @@ public void testCRR_RAOPApproval() { "SUBMIT_DATE = Wed Feb 6 17:08:30 2013 UTC \n"; int raopId = 1111111; - JdbcCrrDao dao = new JdbcCrrDao(); + JdbcCrrDao dao = new JdbcCrrDao(jdbcTemplate); data = dao.updateDataCol_StatusRaop(data, CrrManagerService.CRR_STATUS.APPROVED.toString(), raopId); //System.out.println(data); String expectedData = diff --git a/src/test/java/uk/ac/ngs/dao/JdbcCertificateDaoTest.java b/src/test/java/uk/ac/ngs/dao/JdbcCertificateDaoTest.java index db83e1e..22c3531 100644 --- a/src/test/java/uk/ac/ngs/dao/JdbcCertificateDaoTest.java +++ b/src/test/java/uk/ac/ngs/dao/JdbcCertificateDaoTest.java @@ -13,24 +13,37 @@ package uk.ac.ngs.dao; import java.util.HashMap; +import java.util.List; import java.util.Map; + import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import uk.ac.ngs.common.Pair; import uk.ac.ngs.dao.JdbcCertificateDao.WHERE_PARAMS; +import uk.ac.ngs.domain.CertificateRow; /** * * @author David Meredith */ public class JdbcCertificateDaoTest { - - public JdbcCertificateDaoTest() { - } + + private NamedParameterJdbcTemplate jdbcTemplate; + private JdbcCertificateDao jdbcCertificateDao; @BeforeClass public static void setUpClass() { @@ -42,6 +55,8 @@ public static void tearDownClass() { @Before public void setUp() { + jdbcTemplate = mock(NamedParameterJdbcTemplate.class); + jdbcCertificateDao = new JdbcCertificateDao(jdbcTemplate); } @After @@ -62,19 +77,18 @@ public void testFindBy() { Integer limit = 10; Integer offset = 0; - JdbcCertificateDao instance = new JdbcCertificateDao(); String expResult = "select cert_key, data, dn, cn, email, status, role, notafter from certificate where cn like :cn and role like :role and status like :status LIMIT :limit OFFSET :offset"; - Pair> p = instance.buildQuery(JdbcCertificateDao.SELECT_PROJECT, params, limit, offset, false); + Pair> p = jdbcCertificateDao.buildQuery(JdbcCertificateDao.SELECT_PROJECT, params, limit, offset, false); System.out.println(p.first); assertEquals(expResult, p.first); expResult = "select count(*) from certificate where cn like :cn and role like :role and status like :status LIMIT :limit OFFSET :offset"; - Pair> p2 = instance.buildQuery(JdbcCertificateDao.SELECT_COUNT, params, limit, offset, false); + Pair> p2 = jdbcCertificateDao.buildQuery(JdbcCertificateDao.SELECT_COUNT, params, limit, offset, false); System.out.println(p2.first); assertEquals(expResult, p2.first); expResult = "select count(*) from certificate"; - Pair> p3 = instance.buildQuery(JdbcCertificateDao.SELECT_COUNT, null, null, null, false); + Pair> p3 = jdbcCertificateDao.buildQuery(JdbcCertificateDao.SELECT_COUNT, null, null, null, false); System.out.println(p3.first); assertEquals(expResult, p3.first); @@ -84,7 +98,63 @@ public void testFindBy() { //fail("The test case is a prototype."); } + @Test + public void testFindActiveUserAndRAOperatorBy_withValidInputs_returnsResults() { + String ou = "OU1"; + String o = "O1"; + String loc = "LOC1"; + + List expectedRows = List.of(new CertificateRow(), new CertificateRow()); + + // Stub jdbcTemplate + when(jdbcTemplate.query(anyString(), anyMap(), ArgumentMatchers.>any())) + .thenReturn(expectedRows); + + // Act + List result = jdbcCertificateDao.findActiveUserAndRAOperatorBy(ou, o, loc); + + // Assert + assertEquals(2, result.size()); + + // Capture the query string + ArgumentCaptor queryCaptor = ArgumentCaptor.forClass(String.class); + verify(jdbcTemplate).query(queryCaptor.capture(), anyMap(), ArgumentMatchers.>any()); + + String capturedQuery = queryCaptor.getValue(); + + // Assert parts of the query + assertTrue(capturedQuery.contains("role = 'RA Operator'")); + assertTrue(capturedQuery.contains("role = 'User'")); + assertTrue(capturedQuery.contains("dn like :ra")); + assertTrue(capturedQuery.contains("status = 'VALID'")); + assertTrue(capturedQuery.contains("notafter > :current_time")); + } + + @Test + public void testFindActiveCAs_withValidInputs_returnsResults() { + + List expectedRows = List.of(new CertificateRow(), new CertificateRow()); + // Stub jdbcTemplate + when(jdbcTemplate.query(anyString(), anyMap(), ArgumentMatchers.>any())) + .thenReturn(expectedRows); + // Act + List result = jdbcCertificateDao.findActiveCAs(); + + // Assert + assertEquals(2, result.size()); + + // Capture the query string + ArgumentCaptor queryCaptor = ArgumentCaptor.forClass(String.class); + verify(jdbcTemplate).query(queryCaptor.capture(), anyMap(), ArgumentMatchers.>any()); + + String capturedQuery = queryCaptor.getValue(); + + // Assert parts of the query + assertTrue(capturedQuery.contains("role = 'CA Operator'")); + assertTrue(capturedQuery.contains("status = 'VALID'")); + assertTrue(capturedQuery.contains("notafter > :current_time")); + } } diff --git a/src/test/java/uk/ac/ngs/service/CertificateServiceTests.java b/src/test/java/uk/ac/ngs/service/CertificateServiceTests.java index d3bb825..f0396ec 100644 --- a/src/test/java/uk/ac/ngs/service/CertificateServiceTests.java +++ b/src/test/java/uk/ac/ngs/service/CertificateServiceTests.java @@ -14,8 +14,10 @@ package uk.ac.ngs.service; import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -30,13 +32,16 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.validation.Errors; import uk.ac.ngs.common.MutableConfigParams; import uk.ac.ngs.dao.JdbcCertificateDao; +import uk.ac.ngs.dao.RoleChangeRequestRepository; import uk.ac.ngs.domain.CertificateRow; +import uk.ac.ngs.domain.RoleChangeRequest; import uk.ac.ngs.service.email.EmailService; @RunWith(MockitoJUnitRunner.class) @@ -47,6 +52,12 @@ public class CertificateServiceTests { MutableConfigParams mutableConfigParams; @Mock EmailService emailService; + @Mock + private RoleChangeRequestRepository roleChangeRequestRepository; + @Mock + private CertificateRow targetCert; + @Mock + private CertificateRow currentUser; private String requesterDn = "CN=TestUser"; private long cert_key = 1000L; @@ -64,6 +75,7 @@ public void setUp() { certificateService.setJdbcCertificateDao(jdbcCertDao); certificateService.setMutableConfigParams(mutableConfigParams); certificateService.setEmailService(emailService); + certificateService.setRoleChangeRequestRepository(roleChangeRequestRepository); } @Test @@ -188,4 +200,42 @@ public void testUpdateCertificateRowEmailSendEmailToBothIfOldEmailPresent() thro verify(emailService, times(1)) .sendEmailToOldAndNewOnEmailChange(certRow.getDn(), requesterDn, oldEmail, newEmail, cert_key); } + + @Test + public void testRaiseRoleChangeRequestSavesRequestAndSendsEmail() { + CertificateRow certRow = new CertificateRow(); + certRow.setCn("VALID.CN"); + certRow.setDn("VALID.DN"); + certRow.setEmail("VALID.EMAIL"); + certRow.setStatus("VALID"); + certRow.setNotAfter(datePlusOneDay); + + long certKey = 123L; + String newRole = "RAOP"; + long currentUserCertKey = 456L; + + when(currentUser.getCert_key()).thenReturn(currentUserCertKey); + + RoleChangeRequest mockSavedRequest = mock(RoleChangeRequest.class); + when(mockSavedRequest.getId()).thenReturn(789); + when(roleChangeRequestRepository.save(any(RoleChangeRequest.class))).thenReturn(mockSavedRequest); + when(jdbcCertDao.findActiveCAs()).thenReturn(new ArrayList<>(Arrays.asList(certRow))); + + // Act + certificateService.raiseRoleChangeRequest(certKey, targetCert, newRole, currentUser); + + // Assert + ArgumentCaptor captor = ArgumentCaptor.forClass(RoleChangeRequest.class); + verify(roleChangeRequestRepository).save(captor.capture()); + + RoleChangeRequest capturedRequest = captor.getValue(); + assertEquals((Long) certKey, capturedRequest.getCertKey()); + assertEquals(newRole, capturedRequest.getRequestedRole()); + assertEquals((Long) currentUserCertKey, capturedRequest.getRequestedBy()); + assertEquals(LocalDate.now(), capturedRequest.getRequestedOn()); + + verify(emailService).sendAdminsOnRaopRoleRequest(any(), any(), any(), any()); + verify(emailService).sendRaOnRaopRoleRequest(any(), any(), any()); + verify(emailService).sendUserOnRaopRoleRequest(any(), any(), any()); + } } diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties new file mode 100644 index 0000000..bff4f4f --- /dev/null +++ b/src/test/resources/application.properties @@ -0,0 +1,53 @@ + +# DB connection parameters: +jdbc.driverClassName=org.postgresql.Driver +jdbc.url=jdbc:postgresql://youhost.ac.uk:5432/yourdb +jdbc.username=yourdbusername +jdbc.password=yourdbpassword + + +# PKCS10 config params. These values are looked up when creating CSRs: +supported.pkcs10.orgname.oid=eScienceDev +supported.pkcs10.country.oid=UK +supported.pkcs10.min.modulus=2048 +supported.pkcs10.min.exponent=5 + + +# Path to properties file that contains mutable properties that can be changed +# at runtime - these values are always read from this properties file (server restart not required). +# Use forward slashes for Win, e.g. +#mutable.config.params.full.path=C:/Users/full/path/to/mutableproperties.properties +mutable.config.params.full.path=/full/path/to/file/mutableproperties.properties + + +# The base url of the portal. +# This value is used for example when sending emails to RAs and other uses. +base.portal.url=https://portal.ca.grid-support.ac.uk/ + + +# Properties for the mailSender bean. Note, you will probably need to modify +# the 'mailSender' bean properties directly in 'root-context.xml' to configure the bean with +# finer grain control and to configure extra properties beyond those configured here. +# You can use a gmail account for example: +email.host=smtp.gmail.com +email.port=587 +email.username=somebody@gmail.com +email.password=password +# +# Or if you have a mailserver on local host you can set just the 'email.host' +# and then comment out all the 'mailSender' bean properties in 'root-context.xml' +# except for the host property that is defined with the following line: +# '' +#email.host=localhost +#email.port= +#email.username= +#email.password= + +# The portal can (optionally) send emails, this is used for the from address: +email.from=support@grid-support.ac.uk + + +# From https://blog.swdev.ed.ac.uk/2015/06/24/adding-embedded-tomcat-ajp-support-to-a-spring-boot-application/ +tomcat.ajp.port=9090 +tomcat.ajp.remoteauthentication=false +tomcat.ajp.enabled=true \ No newline at end of file