Skip to content

Commit ae75afa

Browse files
authored
Update notification based certificate choice to use v3.1 endpoint (#140)
* SLIB-126 - add util to create callbackUrl with url-token * SLIB-126 - move UrlSafeTokenGenerator and CallbackUrl to common package * SLIB-113 - update notification-based certificate choice path and request object; update relying party UUID used in test data * SLIB-113 - update notification-based certificate choice builder validations to new standard; activate integrations tests with DEMO environment for notification-based flows * SLIB-113 - improve notification-based certificate choice example and Readme test * SLIB-113 - add handling for ACCOUNT_UNUSABLE end result * SLIB-113 - update notification-based certificate choice builder to use SmartIdRequestSetupException and improve javadoc * SLIB-113 - move UrlSafeTokenGeneratorTest to correct package; fix incorrect validation * SLIB-113 - remove todos * SLIB-113 - improve code style in NotificationCertificateChoiceSessionRequestBuilderTest
1 parent 1226988 commit ae75afa

42 files changed

Lines changed: 513 additions & 289 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
44

55
Changes mentioned under 3.1.x version have not been published yet. Will be released when v3.1 is stable.
66

7+
## [3.1.16] - 2025-10-04
8+
- Updated SmartIdRestConnector to use v3.1 notification-based certificate choice endpoint
9+
- Added AccountUnusableException to handle ACCOUNT_UNUSABLE endResult from session status response
10+
711
## [3.1.15] - 2025-09-17
812
- Added CallbackUrlUtil to generate callback URL with token and provides method to validate sessionSecretDigest
913
- Updated DeviceLinkAuthenticationResponseValidator to also validate userChallenge and userChallengeVerifier same device flows.

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1178,9 +1178,6 @@ Jump to [Query session status](#example-of-using-session-status-poller-to-query-
11781178

11791179
### Notification-based certificate choice session
11801180

1181-
> [!CAUTION]
1182-
> The notification-based certificate choice has not yet been updated to be used with Smart-ID API v3.1
1183-
11841181
#### Request parameters
11851182

11861183
* `relyingPartyUUID`: Required. UUID of the Relying Party.
@@ -1206,10 +1203,12 @@ SemanticsIdentifier semanticsIdentifier = new SemanticsIdentifier(
12061203
SemanticsIdentifier.CountryCode.EE, // 2 character ISO 3166-1 alpha-2 country code
12071204
"40504040001"); // identifier (according to country and identity type reference)
12081205

1206+
// Use requested certificate level to validate certificate choice session status OK response.
1207+
CertificateLevel requestedCertificateLevel = CertificateLevel.QSCD; // Certificate level can either be "QUALIFIED", "ADVANCED" or "QSCD"
12091208
NotificationCertificateChoiceSessionResponse certificateChoiceSessionResponse = client
12101209
.createNotificationCertificateChoice()
12111210
.withSemanticsIdentifier(semanticsIdentifier)
1212-
.withCertificateLevel(CertificateLevel.QSCD) // Certificate level can either be "QUALIFIED", "ADVANCED" or "QSCD"
1211+
.withCertificateLevel(requestedCertificateLevel)
12131212
.initCertificateChoice();
12141213

12151214
String sessionId = certificateChoiceSessionResponse.sessionID();
@@ -1431,6 +1430,7 @@ Exception Categories
14311430
These exceptions handle issues related to the user's Smart-ID account or session requirements.
14321431
* `CertificateLevelMismatchException` Thrown when the returned certificate level does not meet the requested level.
14331432
* `DocumentUnusableException` Indicates that the requested document cannot be used for the operation.
1433+
* `UserAccountUnusableException` Thrown when the user's Smart-ID account is not currently usable for the requested operation.
14341434
* Validation and Parsing Exceptions
14351435
These exceptions arise during validation or parsing operations within the library.
14361436
* `CertificateParsingException` Thrown when the X.509 certificate cannot be parsed.
@@ -1499,7 +1499,7 @@ ResteasyClient resteasyClient = new ResteasyClientBuilder()
14991499
.build();
15001500

15011501
SmartIdClient client = new SmartIdClient();
1502-
client.setRelyingPartyUUID("00000000-0000-0000-0000-000000000000");
1502+
client.setRelyingPartyUUID("00000000-0000-4000-8000-000000000000");
15031503
client.setRelyingPartyName("DEMO");
15041504
client.setHostUrl("https://sid.demo.sk.ee/smart-id-rp/v3/");
15051505
client.setConfiguredClient(resteasyClient);

src/main/java/ee/sk/smartid/DeviceLinkCertificateChoiceSessionRequestBuilder.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
import ee.sk.smartid.exception.UnprocessableSmartIdResponseException;
3434
import ee.sk.smartid.exception.permanent.SmartIdRequestSetupException;
3535
import ee.sk.smartid.rest.SmartIdConnector;
36-
import ee.sk.smartid.rest.dao.CertificateChoiceSessionRequest;
36+
import ee.sk.smartid.rest.dao.DeviceLinkCertificateChoiceSessionRequest;
3737
import ee.sk.smartid.rest.dao.DeviceLinkSessionResponse;
3838
import ee.sk.smartid.rest.dao.RequestProperties;
3939
import ee.sk.smartid.util.StringUtil;
@@ -150,8 +150,8 @@ public DeviceLinkCertificateChoiceSessionRequestBuilder withInitialCallbackUrl(S
150150
*/
151151
public DeviceLinkSessionResponse initCertificateChoice() {
152152
validateRequestParameters();
153-
CertificateChoiceSessionRequest certificateChoiceSessionRequest = createCertificateRequest();
154-
DeviceLinkSessionResponse deviceLinkCertificateChoiceSessionResponse = connector.initDeviceLinkCertificateChoice(certificateChoiceSessionRequest);
153+
DeviceLinkCertificateChoiceSessionRequest deviceLinkCertificateChoiceSessionRequest = createCertificateRequest();
154+
DeviceLinkSessionResponse deviceLinkCertificateChoiceSessionResponse = connector.initDeviceLinkCertificateChoice(deviceLinkCertificateChoiceSessionRequest);
155155
validateResponseParameters(deviceLinkCertificateChoiceSessionResponse);
156156
return deviceLinkCertificateChoiceSessionResponse;
157157
}
@@ -169,8 +169,8 @@ private void validateRequestParameters() {
169169
validateInitialCallbackUrl();
170170
}
171171

172-
private CertificateChoiceSessionRequest createCertificateRequest() {
173-
return new CertificateChoiceSessionRequest(
172+
private DeviceLinkCertificateChoiceSessionRequest createCertificateRequest() {
173+
return new DeviceLinkCertificateChoiceSessionRequest(
174174
relyingPartyUUID,
175175
relyingPartyName,
176176
certificateLevel != null ? certificateLevel.name() : null,

src/main/java/ee/sk/smartid/ErrorResultHandler.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import ee.sk.smartid.exception.permanent.SmartIdServerException;
3636
import ee.sk.smartid.exception.useraccount.DocumentUnusableException;
3737
import ee.sk.smartid.exception.useraccount.RequiredInteractionNotSupportedByAppException;
38+
import ee.sk.smartid.exception.useraccount.UserAccountUnusableException;
3839
import ee.sk.smartid.exception.useraction.SessionTimeoutException;
3940
import ee.sk.smartid.exception.useraction.UserRefusedCertChoiceException;
4041
import ee.sk.smartid.exception.useraction.UserRefusedConfirmationMessageException;
@@ -77,6 +78,7 @@ public static void handle(SessionResult sessionResult) {
7778
case "PROTOCOL_FAILURE" -> throw new ProtocolFailureException();
7879
case "EXPECTED_LINKED_SESSION" -> throw new ExpectedLinkedSessionException();
7980
case "SERVER_ERROR" -> throw new SmartIdServerException();
81+
case "ACCOUNT_UNUSABLE" -> throw new UserAccountUnusableException();
8082
default -> throw new UnprocessableSmartIdResponseException("Unexpected session result: " + sessionResult.getEndResult());
8183
}
8284
}

src/main/java/ee/sk/smartid/NotificationCertificateChoiceSessionRequestBuilder.java

Lines changed: 22 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -28,23 +28,23 @@
2828

2929
import java.util.Set;
3030

31-
import org.slf4j.Logger;
32-
import org.slf4j.LoggerFactory;
33-
3431
import ee.sk.smartid.exception.UnprocessableSmartIdResponseException;
3532
import ee.sk.smartid.exception.permanent.SmartIdClientException;
33+
import ee.sk.smartid.exception.permanent.SmartIdRequestSetupException;
3634
import ee.sk.smartid.rest.SmartIdConnector;
37-
import ee.sk.smartid.rest.dao.CertificateChoiceSessionRequest;
35+
import ee.sk.smartid.rest.dao.NotificationCertificateChoiceSessionRequest;
3836
import ee.sk.smartid.rest.dao.NotificationCertificateChoiceSessionResponse;
3937
import ee.sk.smartid.rest.dao.RequestProperties;
4038
import ee.sk.smartid.rest.dao.SemanticsIdentifier;
4139
import ee.sk.smartid.util.StringUtil;
4240

41+
/**
42+
* Builder for notification-based certificate choice session requests
43+
*/
4344
public class NotificationCertificateChoiceSessionRequestBuilder {
4445

45-
private static final Logger logger = LoggerFactory.getLogger(NotificationCertificateChoiceSessionRequestBuilder.class);
46-
4746
private final SmartIdConnector connector;
47+
4848
private String relyingPartyUUID;
4949
private String relyingPartyName;
5050
private CertificateLevel certificateLevel;
@@ -142,72 +142,53 @@ public NotificationCertificateChoiceSessionRequestBuilder withSemanticsIdentifie
142142
}
143143

144144
/**
145-
* Sends the notification request and get the init session response
146-
* <p>
147-
* There are 2 supported ways to start authentication session:
148-
* <ul>
149-
* <li>with semantics identifier by using {@link #withSemanticsIdentifier(SemanticsIdentifier)}</li>
150-
* </ul>
145+
* Initializes a notification-based certificate choice session
151146
*
152147
* @return init session response
148+
* @throws SmartIdRequestSetupException whe the provided request parameters are invalid
149+
* @throws UnprocessableSmartIdResponseException when the response is missing required parameters
150+
* @throws SmartIdClientException when the request could not be sent
153151
*/
154152
public NotificationCertificateChoiceSessionResponse initCertificateChoice() {
155153
validateRequestParameters();
156-
CertificateChoiceSessionRequest request = createCertificateChoiceRequest();
154+
NotificationCertificateChoiceSessionRequest request = createCertificateChoiceRequest();
157155
NotificationCertificateChoiceSessionResponse notificationCertificateChoiceSessionResponse = initCertificateChoiceSession(request);
158156
validateResponseParameters(notificationCertificateChoiceSessionResponse);
159157
return notificationCertificateChoiceSessionResponse;
160158
}
161159

162-
private NotificationCertificateChoiceSessionResponse initCertificateChoiceSession(CertificateChoiceSessionRequest request) {
160+
private NotificationCertificateChoiceSessionResponse initCertificateChoiceSession(NotificationCertificateChoiceSessionRequest request) {
163161
if (semanticsIdentifier == null) {
164-
throw new SmartIdClientException("SemanticsIdentifier must be set.");
162+
throw new SmartIdRequestSetupException("Value for 'semanticIdentifier' must be set");
165163
}
166164
return connector.initNotificationCertificateChoice(request, semanticsIdentifier);
167165
}
168166

169167
private void validateRequestParameters() {
170168
if (StringUtil.isEmpty(relyingPartyUUID)) {
171-
logger.error("Parameter relyingPartyUUID must be set");
172-
throw new SmartIdClientException("Parameter relyingPartyUUID must be set");
169+
throw new SmartIdRequestSetupException("Value for 'relyingPartyUUID' cannot be empty");
173170
}
174171
if (StringUtil.isEmpty(relyingPartyName)) {
175-
logger.error("Parameter relyingPartyName must be set");
176-
throw new SmartIdClientException("Parameter relyingPartyName must be set");
172+
throw new SmartIdRequestSetupException("Value for 'relyingPartyName' cannot be empty");
173+
}
174+
if (nonce != null && (nonce.isEmpty() || nonce.length() > 30)) {
175+
throw new SmartIdRequestSetupException("Value for 'nonce' length must be between 1 and 30 characters");
177176
}
178-
validateNonce();
179177
}
180178

181-
private CertificateChoiceSessionRequest createCertificateChoiceRequest() {
182-
return new CertificateChoiceSessionRequest(
179+
private NotificationCertificateChoiceSessionRequest createCertificateChoiceRequest() {
180+
return new NotificationCertificateChoiceSessionRequest(
183181
relyingPartyUUID,
184182
relyingPartyName,
185183
certificateLevel != null ? certificateLevel.name() : null,
186184
nonce,
187185
capabilities,
188-
shareMdClientIpAddress != null ? new RequestProperties(shareMdClientIpAddress) : null,
189-
null
190-
);
191-
}
192-
193-
private void validateNonce() {
194-
if (nonce == null) {
195-
return;
196-
}
197-
if (nonce.isEmpty()) {
198-
logger.error("Parameter nonce value has to be at least 1 character long");
199-
throw new SmartIdClientException("Parameter nonce value has to be at least 1 character long");
200-
}
201-
if (nonce.length() > 30) {
202-
logger.error("Nonce cannot be longer that 30 chars");
203-
throw new SmartIdClientException("Nonce cannot be longer that 30 chars");
204-
}
186+
shareMdClientIpAddress != null ? new RequestProperties(shareMdClientIpAddress) : null);
205187
}
206188

207189
private void validateResponseParameters(NotificationCertificateChoiceSessionResponse notificationCertificateChoiceSessionResponse) {
208190
if (StringUtil.isEmpty(notificationCertificateChoiceSessionResponse.getSessionID())) {
209-
logger.error("Session ID is missing from the response");
210-
throw new UnprocessableSmartIdResponseException("Session ID is missing from the response");
191+
throw new UnprocessableSmartIdResponseException("Notification-based certificate choice response field 'sessionID' is missing or empty");
211192
}
212193
}
213194
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package ee.sk.smartid.exception.useraccount;
2+
3+
/*-
4+
* #%L
5+
* Smart ID sample Java client
6+
* %%
7+
* Copyright (C) 2018 - 2025 SK ID Solutions AS
8+
* %%
9+
* Permission is hereby granted, free of charge, to any person obtaining a copy
10+
* of this software and associated documentation files (the "Software"), to deal
11+
* in the Software without restriction, including without limitation the rights
12+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
* copies of the Software, and to permit persons to whom the Software is
14+
* furnished to do so, subject to the following conditions:
15+
*
16+
* The above copyright notice and this permission notice shall be included in
17+
* all copies or substantial portions of the Software.
18+
*
19+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
* THE SOFTWARE.
26+
* #L%
27+
*/
28+
29+
import ee.sk.smartid.exception.UserAccountException;
30+
31+
32+
public class UserAccountUnusableException extends UserAccountException {
33+
34+
public UserAccountUnusableException() {
35+
super("The account is currently unusable");
36+
}
37+
}

src/main/java/ee/sk/smartid/rest/SmartIdConnector.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,18 @@
3333

3434
import ee.sk.smartid.exception.SessionNotFoundException;
3535
import ee.sk.smartid.rest.dao.CertificateByDocumentNumberRequest;
36+
import ee.sk.smartid.rest.dao.DeviceLinkCertificateChoiceSessionRequest;
3637
import ee.sk.smartid.rest.dao.CertificateResponse;
38+
import ee.sk.smartid.rest.dao.DeviceLinkAuthenticationSessionRequest;
39+
import ee.sk.smartid.rest.dao.DeviceLinkSessionResponse;
3740
import ee.sk.smartid.rest.dao.LinkedSignatureSessionRequest;
3841
import ee.sk.smartid.rest.dao.LinkedSignatureSessionResponse;
3942
import ee.sk.smartid.rest.dao.NotificationAuthenticationSessionRequest;
40-
import ee.sk.smartid.rest.dao.SemanticsIdentifier;
41-
import ee.sk.smartid.rest.dao.DeviceLinkAuthenticationSessionRequest;
42-
import ee.sk.smartid.rest.dao.CertificateChoiceSessionRequest;
43-
import ee.sk.smartid.rest.dao.DeviceLinkSessionResponse;
4443
import ee.sk.smartid.rest.dao.NotificationAuthenticationSessionResponse;
44+
import ee.sk.smartid.rest.dao.NotificationCertificateChoiceSessionRequest;
4545
import ee.sk.smartid.rest.dao.NotificationCertificateChoiceSessionResponse;
4646
import ee.sk.smartid.rest.dao.NotificationSignatureSessionResponse;
47+
import ee.sk.smartid.rest.dao.SemanticsIdentifier;
4748
import ee.sk.smartid.rest.dao.SessionStatus;
4849
import ee.sk.smartid.rest.dao.SignatureSessionRequest;
4950

@@ -72,7 +73,7 @@ public interface SmartIdConnector extends Serializable {
7273
* @param request CertificateChoiceSessionRequest containing necessary parameters
7374
* @return DeviceLinkSessionResponse containing sessionID, sessionToken, sessionSecret and deviceLinkBase URL.
7475
*/
75-
DeviceLinkSessionResponse initDeviceLinkCertificateChoice(CertificateChoiceSessionRequest request);
76+
DeviceLinkSessionResponse initDeviceLinkCertificateChoice(DeviceLinkCertificateChoiceSessionRequest request);
7677

7778
/**
7879
* Initiates a linked notification based signature session.
@@ -86,15 +87,15 @@ public interface SmartIdConnector extends Serializable {
8687
* Initiates a notification based certificate choice request.
8788
*
8889
* @param request CertificateChoiceSessionRequest containing necessary parameters
89-
* @param semanticsIdentifier The semantics identifier
90+
* @param semanticsIdentifier The semantics identifier to be used for the session
9091
* @return NotificationCertificateChoiceSessionResponse containing sessionID
9192
*/
92-
NotificationCertificateChoiceSessionResponse initNotificationCertificateChoice(CertificateChoiceSessionRequest request, SemanticsIdentifier semanticsIdentifier);
93+
NotificationCertificateChoiceSessionResponse initNotificationCertificateChoice(NotificationCertificateChoiceSessionRequest request, SemanticsIdentifier semanticsIdentifier);
9394

9495
/**
9596
* Queries signing certificate by document number.
9697
*
97-
* @param request CertificateByDocumentNumberRequest containing necessary parameters
98+
* @param request CertificateByDocumentNumberRequest containing necessary parameters
9899
* @param documentNumber The document number
99100
* @return CertificateResponse containing response state and certificate information.
100101
*/

src/main/java/ee/sk/smartid/rest/SmartIdRestConnector.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,15 @@
4545
import ee.sk.smartid.exception.useraccount.PersonShouldViewSmartIdPortalException;
4646
import ee.sk.smartid.exception.useraccount.UserAccountNotFoundException;
4747
import ee.sk.smartid.rest.dao.CertificateByDocumentNumberRequest;
48-
import ee.sk.smartid.rest.dao.CertificateChoiceSessionRequest;
48+
import ee.sk.smartid.rest.dao.DeviceLinkCertificateChoiceSessionRequest;
4949
import ee.sk.smartid.rest.dao.CertificateResponse;
5050
import ee.sk.smartid.rest.dao.DeviceLinkAuthenticationSessionRequest;
5151
import ee.sk.smartid.rest.dao.DeviceLinkSessionResponse;
5252
import ee.sk.smartid.rest.dao.LinkedSignatureSessionRequest;
5353
import ee.sk.smartid.rest.dao.LinkedSignatureSessionResponse;
5454
import ee.sk.smartid.rest.dao.NotificationAuthenticationSessionRequest;
5555
import ee.sk.smartid.rest.dao.NotificationAuthenticationSessionResponse;
56+
import ee.sk.smartid.rest.dao.NotificationCertificateChoiceSessionRequest;
5657
import ee.sk.smartid.rest.dao.NotificationCertificateChoiceSessionResponse;
5758
import ee.sk.smartid.rest.dao.NotificationSignatureSessionResponse;
5859
import ee.sk.smartid.rest.dao.SemanticsIdentifier;
@@ -85,7 +86,7 @@ public class SmartIdRestConnector implements SmartIdConnector {
8586
private static final String DEVICE_LINK_CERTIFICATE_CHOICE_DEVICE_LINK_PATH = "signature/certificate-choice/device-link/anonymous";
8687
private static final String LINKED_NOTIFICATION_SIGNATURE_WITH_DOCUMENT_NUMBER_PATH = "signature/notification/linked";
8788

88-
private static final String NOTIFICATION_CERTIFICATE_CHOICE_WITH_SEMANTIC_IDENTIFIER_PATH = "/certificatechoice/notification/etsi";
89+
private static final String NOTIFICATION_CERTIFICATE_CHOICE_WITH_SEMANTIC_IDENTIFIER_PATH = "signature/certificate-choice/notification/etsi";
8990

9091
private static final String CERTIFICATE_BY_DOCUMENT_NUMBER_PATH = "/signature/certificate/";
9192

@@ -186,7 +187,7 @@ public NotificationAuthenticationSessionResponse initNotificationAuthentication(
186187
}
187188

188189
@Override
189-
public DeviceLinkSessionResponse initDeviceLinkCertificateChoice(CertificateChoiceSessionRequest request) {
190+
public DeviceLinkSessionResponse initDeviceLinkCertificateChoice(DeviceLinkCertificateChoiceSessionRequest request) {
190191
logger.debug("Initiating device link based certificate choice request");
191192
URI uri = UriBuilder
192193
.fromUri(endpointUrl)
@@ -207,7 +208,7 @@ public LinkedSignatureSessionResponse initLinkedNotificationSignature(LinkedSign
207208
}
208209

209210
@Override
210-
public NotificationCertificateChoiceSessionResponse initNotificationCertificateChoice(CertificateChoiceSessionRequest request, SemanticsIdentifier semanticsIdentifier) {
211+
public NotificationCertificateChoiceSessionResponse initNotificationCertificateChoice(NotificationCertificateChoiceSessionRequest request, SemanticsIdentifier semanticsIdentifier) {
211212
URI uri = UriBuilder
212213
.fromUri(endpointUrl)
213214
.path(NOTIFICATION_CERTIFICATE_CHOICE_WITH_SEMANTIC_IDENTIFIER_PATH)

0 commit comments

Comments
 (0)