diff --git a/src/main/java/ee/sk/siddemo/GlobalExceptionHandler.java b/src/main/java/ee/sk/siddemo/GlobalExceptionHandler.java index 85b2699..ce1a9d9 100644 --- a/src/main/java/ee/sk/siddemo/GlobalExceptionHandler.java +++ b/src/main/java/ee/sk/siddemo/GlobalExceptionHandler.java @@ -46,7 +46,7 @@ public ModelAndView handleSidOperationException(SidOperationException exception) @ExceptionHandler(Exception.class) public ModelAndView handleSmartIdException(Exception exception) { - logger.warn("Generic error caught", exception); + logger.warn("Generic error caught {}", exception.getMessage()); var model = new ModelMap(); model.addAttribute("errorMessage", exception.getMessage()); diff --git a/src/main/java/ee/sk/siddemo/controller/SmartIdV3DynamicLinkCertificateChoiceController.java b/src/main/java/ee/sk/siddemo/controller/SmartIdV3DynamicLinkCertificateChoiceController.java index 63b29d9..6f531c0 100644 --- a/src/main/java/ee/sk/siddemo/controller/SmartIdV3DynamicLinkCertificateChoiceController.java +++ b/src/main/java/ee/sk/siddemo/controller/SmartIdV3DynamicLinkCertificateChoiceController.java @@ -103,7 +103,7 @@ public ModelAndView handleCertificateChoiceSessionsError(@RequestParam(value = " } @GetMapping(value = "/v3/dynamic-link/certificate-choice-result") - public ModelAndView getAuthenticationResult(ModelMap model, HttpSession session) { + public ModelAndView toCertificateChoiceResult(ModelMap model, HttpSession session) { String documentNumber = (String) session.getAttribute("documentNumber"); String distinguishedName = (String) session.getAttribute("distinguishedName"); model.addAttribute("documentNumber", documentNumber); @@ -113,7 +113,7 @@ public ModelAndView getAuthenticationResult(ModelMap model, HttpSession session) } @GetMapping(value = "/v3/dynamic-link/cancel-certificate-choice") - public ModelAndView cancelAuthentication(ModelMap model, HttpServletRequest request) { + public ModelAndView cancelCertificateChoice(ModelMap model, HttpServletRequest request) { resetSession(request); model.addAttribute("activeTab", "rp-api-v3"); return new ModelAndView("v3/main", model); diff --git a/src/main/java/ee/sk/siddemo/controller/SmartIdV3DynamicLinkSignatureController.java b/src/main/java/ee/sk/siddemo/controller/SmartIdV3DynamicLinkSignatureController.java new file mode 100644 index 0000000..0a111aa --- /dev/null +++ b/src/main/java/ee/sk/siddemo/controller/SmartIdV3DynamicLinkSignatureController.java @@ -0,0 +1,149 @@ +package ee.sk.siddemo.controller; + +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; + +import ee.sk.siddemo.exception.SidOperationException; +import ee.sk.siddemo.model.DynamicContent; +import ee.sk.siddemo.model.SigningResult; +import ee.sk.siddemo.model.UserDocumentNumberRequest; +import ee.sk.siddemo.model.UserRequest; +import ee.sk.siddemo.services.DynamicContentService; +import ee.sk.siddemo.services.SmartIdV3DynamicLinkSignatureService; +import ee.sk.siddemo.services.SmartIdV3SessionsStatusService; +import ee.sk.smartid.v3.SessionType; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpSession; + +@Controller +public class SmartIdV3DynamicLinkSignatureController { + + private final Logger logger = LoggerFactory.getLogger(SmartIdV3DynamicLinkSignatureController.class); + + private final SmartIdV3DynamicLinkSignatureService smartIdV3DynamicLinkSignatureService; + private final SmartIdV3SessionsStatusService smartIdV3SessionsStatusService; + private final DynamicContentService dynamicContentService; + + public SmartIdV3DynamicLinkSignatureController(SmartIdV3DynamicLinkSignatureService smartIdV3DynamicLinkSignatureService, + SmartIdV3SessionsStatusService smartIdV3SessionsStatusService, + DynamicContentService dynamicContentService) { + this.smartIdV3DynamicLinkSignatureService = smartIdV3DynamicLinkSignatureService; + this.smartIdV3SessionsStatusService = smartIdV3SessionsStatusService; + this.dynamicContentService = dynamicContentService; + } + + @PostMapping(value = "v3/dynamic-link/start-signing-with-document-number") + public ModelAndView sendDynamicLinkSigningRequestWithDocumentNumber(@ModelAttribute("userDocumentNumberRequest") UserDocumentNumberRequest userDocumentNumberRequest, + BindingResult bindingResult, + ModelMap model, + RedirectAttributes redirectAttributes, + HttpServletRequest request) { + model.addAttribute("activeTab", "rp-api-v3"); + if (userDocumentNumberRequest.getFile() == null || userDocumentNumberRequest.getFile().getOriginalFilename() == null || userDocumentNumberRequest.getFile().isEmpty()) { + bindingResult.rejectValue("file", "error.file", "Please select a file to upload"); + } + + if (bindingResult.hasErrors()) { + logger.debug("Validation errors: {}", bindingResult.getAllErrors()); + redirectAttributes.addFlashAttribute("org.springframework.validation.BindingResult.userDocumentNumberRequest", bindingResult); + redirectAttributes.addFlashAttribute("userDocumentNumberRequest", userDocumentNumberRequest); + return new ModelAndView("redirect:/rp-api-v3"); + } + HttpSession session = resetSession(request); + smartIdV3DynamicLinkSignatureService.startSigningWithDocumentNumber(session, userDocumentNumberRequest); + return new ModelAndView("v3/dynamic-link/signing", model); + } + + @PostMapping(value = "v3/dynamic-link/start-signing-with-person-code") + public ModelAndView sendDynamicLinkSigningRequestWithPersonCode(@ModelAttribute("userRequest") UserRequest userRequest, + BindingResult bindingResult, + ModelMap model, + RedirectAttributes redirectAttributes, + HttpServletRequest request) { + model.addAttribute("activeTab", "rp-api-v3"); + if (userRequest.getFile() == null || userRequest.getFile().getOriginalFilename() == null || userRequest.getFile().isEmpty()) { + bindingResult.rejectValue("file", "error.file", "Please select a file to upload"); + } + + if (bindingResult.hasErrors()) { + logger.debug("Validation errors: {}", bindingResult.getAllErrors()); + redirectAttributes.addFlashAttribute("org.springframework.validation.BindingResult.userRequest", bindingResult); + redirectAttributes.addFlashAttribute("userRequest", userRequest); + return new ModelAndView("redirect:/rp-api-v3"); + } + HttpSession session = resetSession(request); + smartIdV3DynamicLinkSignatureService.startSigningWithPersonCode(session, userRequest); + return new ModelAndView("v3/dynamic-link/signing", model); + } + + @GetMapping(value = "v3/dynamic-link/check-signing-status") + @ResponseBody + public ResponseEntity> checkSigningStatus(HttpSession session) { + boolean checkCompleted; + try { + checkCompleted = smartIdV3DynamicLinkSignatureService.checkSignatureStatus(session); + } catch (SidOperationException ex) { + logger.error("Error occurred while checking authentication status", ex); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Map.of("errorMessage", ex.getMessage())); + } + if (checkCompleted) { + logger.debug("Session status: COMPLETED"); + return ResponseEntity.ok(Map.of("sessionStatus", "COMPLETED")); + } + + logger.debug("Generate dynamic content for session {}", session.getId()); + DynamicContent dynamicContent = dynamicContentService.getDynamicContent(session, SessionType.SIGNATURE); + Map content = new HashMap<>(); + content.put("dynamicLink", dynamicContent.getDynamicLink().toString()); + content.put("qrCode", dynamicContent.getQrCode()); + return ResponseEntity.ok(content); + } + + @GetMapping(value = "/v3/dynamic-link/sign-session-error") + public ModelAndView handleSigningSessionError(@RequestParam(value = "errorMessage", required = false) String errorMessage, + ModelMap model) { + model.addAttribute("errorMessage", errorMessage); + model.addAttribute("activeTab", "rp-api-v3"); + return new ModelAndView("sidOperationError", model); + } + + @GetMapping(value = "/v3/dynamic-link/signing-result") + public ModelAndView toSigningResult(ModelMap model, HttpSession session) { + SigningResult signingResult = smartIdV3DynamicLinkSignatureService.handleSignatureResult(session); + model.addAttribute("signingResult", signingResult); + model.addAttribute("activeTab", "rp-api-v3"); + return new ModelAndView("v3/dynamic-link/signing-result", model); + } + + @GetMapping(value = "/v3/dynamic-link/cancel-signing") + public ModelAndView cancelSigning(ModelMap model, HttpServletRequest request) { + resetSession(request); + return new ModelAndView("redirect:/rp-api-v3", model); + } + + private HttpSession resetSession(HttpServletRequest request) { + HttpSession session = request.getSession(); + if (session != null) { + smartIdV3SessionsStatusService.cancelPolling(session.getId()); + session.invalidate(); + } + // Create a new session + session = request.getSession(true); + return session; + } +} diff --git a/src/main/java/ee/sk/siddemo/controller/SmartIdV3NotificationBasedCertificateChoiceController.java b/src/main/java/ee/sk/siddemo/controller/SmartIdV3NotificationBasedCertificateChoiceController.java index c13e825..aeab301 100644 --- a/src/main/java/ee/sk/siddemo/controller/SmartIdV3NotificationBasedCertificateChoiceController.java +++ b/src/main/java/ee/sk/siddemo/controller/SmartIdV3NotificationBasedCertificateChoiceController.java @@ -106,7 +106,7 @@ public ResponseEntity> checkCertificateChoiceStatus(HttpSess } @GetMapping(value = "/v3/notification-based/certificate-choice-result") - public ModelAndView getAuthenticationResult(ModelMap model, HttpSession session) { + public ModelAndView toCertificateChoiceResult(ModelMap model, HttpSession session) { String distinguishedName = (String) session.getAttribute("distinguishedName"); if (distinguishedName == null) { return new ModelAndView("v3/main", model); @@ -117,7 +117,7 @@ public ModelAndView getAuthenticationResult(ModelMap model, HttpSession session) } @GetMapping(value = "/v3/notification-based/cancel-certificate-choice") - public ModelAndView cancelAuthentication(ModelMap model, HttpServletRequest request) { + public ModelAndView cancelCertificateChoice(ModelMap model, HttpServletRequest request) { resetSession(request); model.addAttribute("activeTab", "rp-api-v3"); return new ModelAndView("v3/main", model); diff --git a/src/main/java/ee/sk/siddemo/model/UserDocumentNumberRequest.java b/src/main/java/ee/sk/siddemo/model/UserDocumentNumberRequest.java index f69f4f5..ad8dcf2 100644 --- a/src/main/java/ee/sk/siddemo/model/UserDocumentNumberRequest.java +++ b/src/main/java/ee/sk/siddemo/model/UserDocumentNumberRequest.java @@ -22,12 +22,15 @@ * #L% */ +import org.springframework.web.multipart.MultipartFile; + import jakarta.validation.constraints.NotNull; public class UserDocumentNumberRequest { @NotNull private String documentNumber; + private MultipartFile file; public String getDocumentNumber() { return documentNumber; @@ -36,4 +39,12 @@ public String getDocumentNumber() { public void setDocumentNumber(String documentNumber) { this.documentNumber = documentNumber; } + + public MultipartFile getFile() { + return file; + } + + public void setFile(MultipartFile file) { + this.file = file; + } } diff --git a/src/main/java/ee/sk/siddemo/services/FileService.java b/src/main/java/ee/sk/siddemo/services/FileService.java new file mode 100644 index 0000000..64d9bde --- /dev/null +++ b/src/main/java/ee/sk/siddemo/services/FileService.java @@ -0,0 +1,36 @@ +package ee.sk.siddemo.services; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +@Service +public class FileService { + + @Value("${app.signed-files-directory}") + private String signedFilesDirectory; + + public String createPath() { + File containerFile = null; + try { + containerFile = File.createTempFile("sid-demo-container-", ".asice"); + } catch (IOException e) { + throw new RuntimeException("Failed to create temporary file", e); + } + Path targetPath = createSavePath(containerFile); + return targetPath.toString(); + } + + private Path createSavePath(File containerFile) { + Path targetDir = Paths.get(signedFilesDirectory); + File directory = targetDir.toFile(); + if (!directory.exists()) { + directory.mkdirs(); + } + return targetDir.resolve(containerFile.getName()); + } +} diff --git a/src/main/java/ee/sk/siddemo/services/SmartIdV2SignatureServiceImpl.java b/src/main/java/ee/sk/siddemo/services/SmartIdV2SignatureServiceImpl.java index ac21578..2ed2643 100644 --- a/src/main/java/ee/sk/siddemo/services/SmartIdV2SignatureServiceImpl.java +++ b/src/main/java/ee/sk/siddemo/services/SmartIdV2SignatureServiceImpl.java @@ -24,10 +24,7 @@ import static java.util.Arrays.asList; -import java.io.File; import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; import org.digidoc4j.Configuration; import org.digidoc4j.Container; @@ -78,15 +75,16 @@ public class SmartIdV2SignatureServiceImpl implements SmartIdV2SignatureService @Value("${sid.v2.client.relyingPartyName}") private String sidRelyingPartyName; - @Value("${app.signed-files-directory}") - private String signedFilesDirectory; - private final SmartIdCertificateService certificateService; private final SmartIdClient smartIdClientV2; + private final FileService fileService; - public SmartIdV2SignatureServiceImpl(SmartIdCertificateService certificateService, SmartIdClient smartIdClientV2) { + public SmartIdV2SignatureServiceImpl(SmartIdCertificateService certificateService, + SmartIdClient smartIdClientV2, + FileService fileService) { this.certificateService = certificateService; this.smartIdClientV2 = smartIdClientV2; + this.fileService = fileService; } @Override @@ -145,7 +143,7 @@ private DataFile getUploadedDataFile(MultipartFile uploadedFile) { @Override public SigningResult sign(SigningSessionInfo signingSessionInfo) { Signature signature; - Path targetPath; + String targetPath; try { @@ -166,31 +164,19 @@ public SigningResult sign(SigningSessionInfo signingSessionInfo) { signature = signingSessionInfo.getDataToSign().finalize(signatureValue); signingSessionInfo.getContainer().addSignature(signature); - File containerFile = File.createTempFile("sid-demo-container-", ".asice"); - targetPath = createSavePath(containerFile); - signingSessionInfo.getContainer().saveAsFile(targetPath.toString()); + targetPath = fileService.createPath(); + signingSessionInfo.getContainer().saveAsFile(targetPath); } catch (UserAccountNotFoundException | UserRefusedException | UserSelectedWrongVerificationCodeException | SessionTimeoutException | DocumentUnusableException | ServerMaintenanceException e) { logger.warn("Smart-ID service returned internal error that cannot be handled locally."); throw new SidOperationException("Smart-ID internal error", e); - } catch (IOException e) { - throw new SidOperationException("Could not create container file.", e); } return SigningResult.newBuilder() .withResult("Signing successful") .withValid(signature.validateSignature().isValid()) .withTimestamp(signature.getTimeStampCreationTime()) - .withContainerFilePath(targetPath.toString()) + .withContainerFilePath(targetPath) .build(); } - - private Path createSavePath(File containerFile) { - Path targetDir = Paths.get(signedFilesDirectory); - File directory = targetDir.toFile(); - if (!directory.exists()) { - directory.mkdirs(); - } - return targetDir.resolve(containerFile.getName()); - } } diff --git a/src/main/java/ee/sk/siddemo/services/SmartIdV3DynamicLinkSignatureService.java b/src/main/java/ee/sk/siddemo/services/SmartIdV3DynamicLinkSignatureService.java new file mode 100644 index 0000000..ede0348 --- /dev/null +++ b/src/main/java/ee/sk/siddemo/services/SmartIdV3DynamicLinkSignatureService.java @@ -0,0 +1,198 @@ +package ee.sk.siddemo.services; + +import java.io.IOException; +import java.security.cert.X509Certificate; +import java.time.Instant; +import java.util.List; +import java.util.Optional; + +import org.digidoc4j.Configuration; +import org.digidoc4j.Container; +import org.digidoc4j.ContainerBuilder; +import org.digidoc4j.DataFile; +import org.digidoc4j.DataToSign; +import org.digidoc4j.DigestAlgorithm; +import org.digidoc4j.Signature; +import org.digidoc4j.SignatureBuilder; +import org.digidoc4j.SignatureProfile; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import ee.sk.siddemo.exception.FileUploadException; +import ee.sk.siddemo.exception.SidOperationException; +import ee.sk.siddemo.model.SigningResult; +import ee.sk.siddemo.model.UserDocumentNumberRequest; +import ee.sk.siddemo.model.UserRequest; +import ee.sk.smartid.CertificateParser; +import ee.sk.smartid.exception.useraction.SessionTimeoutException; +import ee.sk.smartid.rest.dao.SemanticsIdentifier; +import ee.sk.smartid.v3.CertificateLevel; +import ee.sk.smartid.v3.SignableData; +import ee.sk.smartid.v3.SignatureResponseMapper; +import ee.sk.smartid.v3.SingatureResponse; +import ee.sk.smartid.v3.SmartIdClient; +import ee.sk.smartid.v3.rest.dao.DynamicLinkInteraction; +import ee.sk.smartid.v3.rest.dao.DynamicLinkSessionResponse; +import ee.sk.smartid.v3.rest.dao.SessionCertificate; +import ee.sk.smartid.v3.rest.dao.SessionStatus; +import jakarta.servlet.http.HttpSession; + +@Service +public class SmartIdV3DynamicLinkSignatureService { + + private static final Logger logger = LoggerFactory.getLogger(SmartIdV3DynamicLinkSignatureService.class); + + private final SmartIdV3NotificationBasedCertificateChoiceService certificateChoiceService; + private final SmartIdV3SessionsStatusService sessionsStatusService; + private final SmartIdClient smartIdClientV3; + private final FileService fileService; + + public SmartIdV3DynamicLinkSignatureService(SmartIdV3NotificationBasedCertificateChoiceService certificateChoiceService, + SmartIdV3SessionsStatusService sessionsStatusService, + SmartIdClient smartIdClientV3, FileService fileService) { + this.certificateChoiceService = certificateChoiceService; + this.sessionsStatusService = sessionsStatusService; + this.smartIdClientV3 = smartIdClientV3; + this.fileService = fileService; + } + + public void startSigningWithDocumentNumber(HttpSession session, UserDocumentNumberRequest userDocumentNumberRequest) { + certificateChoiceService.startCertificateChoice(session, userDocumentNumberRequest); + var signableData = toSignableData(userDocumentNumberRequest.getFile(), getX509Certificate(session), session); + + DynamicLinkSessionResponse sessionResponse = smartIdClientV3.createDynamicLinkSignature() + .withCertificateLevel(CertificateLevel.QUALIFIED) + .withSignableData(signableData) + .withAllowedInteractionsOrder(List.of(DynamicLinkInteraction.displayTextAndPIN("Sign the document!"))) + .withDocumentNumber(userDocumentNumberRequest.getDocumentNumber()) + .initSignatureSession(); + Instant responseReceivedTime = Instant.now(); + + saveResponseAttributes(session, sessionResponse, responseReceivedTime); + + sessionsStatusService.startPolling(session, sessionResponse.getSessionID()); + } + + public void startSigningWithPersonCode(HttpSession session, UserRequest userRequest) { + certificateChoiceService.startCertificateChoice(session, userRequest); + var signableData = toSignableData(userRequest.getFile(), getX509Certificate(session), session); + + var semanticsIdentifier = new SemanticsIdentifier(SemanticsIdentifier.IdentityType.PNO, userRequest.getCountry(), userRequest.getNationalIdentityNumber()); + + DynamicLinkSessionResponse sessionResponse = smartIdClientV3.createDynamicLinkSignature() + .withCertificateLevel(CertificateLevel.QUALIFIED) + .withSignableData(signableData) + .withSemanticsIdentifier(semanticsIdentifier) + .withAllowedInteractionsOrder(List.of(DynamicLinkInteraction.displayTextAndPIN("Sign the document!"))) + .initSignatureSession(); + Instant responseReceivedTime = Instant.now(); + + saveResponseAttributes(session, sessionResponse, responseReceivedTime); + + sessionsStatusService.startPolling(session, sessionResponse.getSessionID()); + } + + public boolean checkSignatureStatus(HttpSession session) { + Optional sessionStatus = sessionsStatusService.getSessionsStatus(session.getId()); + return sessionStatus + .map(status -> { + if (status.getState().equals("COMPLETE")) { + saveValidateResponse(session, status); + session.setAttribute("session_status", "COMPLETED"); + logger.debug("Mobile device IP address: {}", status.getDeviceIpAddress()); + return true; + } + return false; + }) + .orElse(false); + } + + public SigningResult handleSignatureResult(HttpSession session) { + var dynamicLinkSignatureResponse = (SingatureResponse) session.getAttribute("signing_response"); + if (dynamicLinkSignatureResponse == null) { + throw new SidOperationException("No signature response found in session"); + } + + byte[] signatureValue = dynamicLinkSignatureResponse.getSignatureValue(); + DataToSign dataToSign = (DataToSign) session.getAttribute("dataToSign"); + Signature signature = dataToSign.finalize(signatureValue); + + Container container = (Container) session.getAttribute("container"); + container.addSignature(signature); + + String filePath = fileService.createPath(); + container.saveAsFile(filePath); + return SigningResult.newBuilder() + .withResult("Signing successful") + .withValid(signature.validateSignature().isValid()) + .withTimestamp(signature.getTimeStampCreationTime()) + .withContainerFilePath(filePath) + .build(); + } + + private SignableData toSignableData(MultipartFile file, X509Certificate signingCertificate, HttpSession session) { + Container container = toContainer(file); + DataToSign dataToSign = toDataToSign(container, signingCertificate); + saveSigningAttributes(session, container, dataToSign); + return new SignableData(dataToSign.getDataToSign()); + } + + private Container toContainer(MultipartFile userDocumentNumberRequest) { + DataFile uploadedFile = getUploadedDataFile(userDocumentNumberRequest); + + var configuration = new Configuration(Configuration.Mode.TEST); + return ContainerBuilder.aContainer() + .withConfiguration(configuration) + .withDataFile(uploadedFile) + .build(); + } + + private DataFile getUploadedDataFile(MultipartFile uploadedFile) { + try { + return new DataFile(uploadedFile.getInputStream(), uploadedFile.getOriginalFilename(), uploadedFile.getContentType()); + } catch (IOException e) { + throw new FileUploadException(e.getCause()); + } + } + + private X509Certificate getX509Certificate(HttpSession session) { + Optional certSessionStatus; + do { + certSessionStatus = sessionsStatusService.getSessionsStatus(session.getId()); + } while (certSessionStatus.isEmpty()); + + SessionCertificate sessionCertificate = certSessionStatus.get().getCert(); + return CertificateParser.parseX509Certificate(sessionCertificate.getValue()); + } + + private static void saveSigningAttributes(HttpSession session, Container container, DataToSign dataToSign) { + session.setAttribute("container", container); + session.setAttribute("dataToSign", dataToSign); + } + + private static void saveResponseAttributes(HttpSession session, DynamicLinkSessionResponse sessionResponse, Instant responseReceivedTime) { + session.setAttribute("sessionSecret", sessionResponse.getSessionSecret()); + session.setAttribute("sessionToken", sessionResponse.getSessionToken()); + session.setAttribute("sessionID", sessionResponse.getSessionID()); + session.setAttribute("responseReceivedTime", responseReceivedTime); + } + + private static DataToSign toDataToSign(Container container, X509Certificate certificate) { + return SignatureBuilder.aSignature(container) + .withSigningCertificate(certificate) + .withSignatureDigestAlgorithm(DigestAlgorithm.SHA512) + .withSignatureProfile(SignatureProfile.LT) + .buildDataToSign(); + } + + private static void saveValidateResponse(HttpSession session, SessionStatus status) { + try { + var dynamicLinkSignatureResponse = SignatureResponseMapper.from(status, CertificateLevel.QUALIFIED.name()); + session.setAttribute("signing_response", dynamicLinkSignatureResponse); + } catch (SessionTimeoutException ex) { + throw new SidOperationException(ex.getMessage()); + } + } +} diff --git a/src/main/resources/templates/error.html b/src/main/resources/templates/error.html index 1265c54..eaae551 100644 --- a/src/main/resources/templates/error.html +++ b/src/main/resources/templates/error.html @@ -40,10 +40,16 @@
diff --git a/src/main/resources/templates/v3/dynamic-link/certificate-choice.html b/src/main/resources/templates/v3/dynamic-link/certificate-choice.html index d6f6092..80c5960 100644 --- a/src/main/resources/templates/v3/dynamic-link/certificate-choice.html +++ b/src/main/resources/templates/v3/dynamic-link/certificate-choice.html @@ -6,7 +6,7 @@ Smart-ID - + + + + +
+ +
+
+
+
+

+ Please scan the QR code with the Smart-ID app to proceed +

+
+
+ QR code +
+
+ +
+
+

+ Link option is only meant to work on mobile device where Smart-ID app is installed. + If app is not available on your device, then link should direct you to the app store. +

+
+ +
+
Link content
+
+ +
+
+
+
+ Cancel +
+
+
+ + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/v3/main.html b/src/main/resources/templates/v3/main.html index 9db381c..bf3b73d 100644 --- a/src/main/resources/templates/v3/main.html +++ b/src/main/resources/templates/v3/main.html @@ -34,6 +34,7 @@
+
@@ -144,6 +145,7 @@

Smart-ID

+
@@ -248,8 +250,114 @@

Smart-ID

+
- Signing tab +
+ + + +
+
+ + +
+ Notification based signing with person code +
+
+ Notification based signing with document number +
+
+
+
@@ -278,48 +386,35 @@

Smart-ID

} }); - // handle auth content switching - const authButtons = document.querySelectorAll('.auth-btn'); - const authAreas = document.querySelectorAll('.auth-area'); - authButtons.forEach(button => { - button.onclick = function () { - authButtons.forEach(button => { - button.classList.remove('active'); - }); - authAreas.forEach(area => { - area.classList.remove('active'); - area.classList.add('d-none'); - }); - button.classList.add('active'); + // handle area switching + function connectButtonClickToArea(buttonClass, areaClass) { + const buttons = document.querySelectorAll(buttonClass); + const areas = document.querySelectorAll(areaClass); + buttons.forEach(button => { + button.onclick = function () { + // hide all buttons and areas + buttons.forEach(button => { + button.classList.remove('active'); + }); + areas.forEach(area => { + area.classList.remove('active'); + area.classList.add('d-none'); + }); - const areaId = button.getAttribute('aria-controls'); - let area = document.getElementById(areaId); - area.classList.add('active'); - area.classList.remove('d-none'); - } - }); + // show the clicked button and related area + button.classList.add('active'); - // handle certificate choice content switching - const certButtons = document.querySelectorAll('.certification-choice-btn'); - const certAreas = document.querySelectorAll('.certification-choice-area'); - certButtons.forEach(button => { - button.onclick = function () { - certButtons.forEach(button => { - button.classList.remove('active'); - }); - certAreas.forEach(area => { - area.classList.remove('active'); - area.classList.add('d-none'); - }); - button.classList.add('active'); + const areaId = button.getAttribute('aria-controls'); + let area = document.getElementById(areaId); + area.classList.add('active'); + area.classList.remove('d-none'); + } + }); + } - const areaId = button.getAttribute('aria-controls'); - console.log(areaId); - let area = document.getElementById(areaId); - area.classList.add('active'); - area.classList.remove('d-none'); - } - }); + connectButtonClickToArea('.auth-btn', '.auth-area'); + connectButtonClickToArea('.certification-choice-btn', '.certification-choice-area'); + connectButtonClickToArea('.signing-btn', '.signing-area'); \ No newline at end of file