Skip to content

Commit

Permalink
SDK-2256: Add Digital Identity Session QR code retrieval service
Browse files Browse the repository at this point in the history
  • Loading branch information
irotech committed Jul 25, 2023
1 parent cd5d1c8 commit b45d59a
Show file tree
Hide file tree
Showing 8 changed files with 182 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,15 @@ public ShareSessionQrCode createShareQrCode(String sessionId) throws DigitalIden
return identityService.createShareQrCode(sdkId, keyPair, sessionId);
}

public Object fetchShareQrCode(String qrCodeId) {
return identityService.fetchShareQrCode(qrCodeId);
/**
* Retrieve the sharing session QR code
*
* @param qrCodeId ID of the QR code to retrieve
* @return The content of the Share Session QR code
* @throws DigitalIdentityException Thrown if the QR code retrieval is unsuccessful
*/
public ShareSessionQrCode fetchShareQrCode(String qrCodeId) throws DigitalIdentityException {
return identityService.fetchShareQrCode(sdkId, keyPair, qrCodeId);
}

public Object fetchShareReceipt(String receiptId) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package com.yoti.api.client.identity;

import java.net.URI;
import java.util.List;

import com.yoti.api.client.identity.extension.Extension;

import com.fasterxml.jackson.annotation.JsonProperty;

public class ShareSessionQrCode {
Expand All @@ -8,20 +13,60 @@ public class ShareSessionQrCode {
private String id;

@JsonProperty(Property.URI)
private String uri;
private URI uri;

@JsonProperty(Property.EXPIRY)
private String expiry;

@JsonProperty(Property.POLICY)
private String policy;

@JsonProperty(Property.EXTENSIONS)
private List<Extension<?>> extensions;

@JsonProperty(Property.SESSION)
private ShareSession session;

@JsonProperty(Property.REDIRECT_URI)
private URI redirectUri;

public String getId() {
return id;
}

public String getUri() {
public URI getUri() {
return uri;
}

public String getExpiry() {
return expiry;
}

public String getPolicy() {
return policy;
}

public List<Extension<?>> getExtensions() {
return extensions;
}

public ShareSession getSession() {
return session;
}

public URI getRedirectUri() {
return redirectUri;
}

private static final class Property {

private static final String ID = "id";
private static final String URI = "uri";
private static final String EXPIRY = "expiry";
private static final String POLICY = "policy";
private static final String EXTENSIONS = "extensions";
private static final String SESSION = "session";
private static final String REDIRECT_URI = "redirectUri";

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public class UnsignedPathFactory {
// Share V2
private static final String IDENTITY_SESSION_CREATION = "/v2/sessions";
private static final String IDENTITY_SESSION_QR_CODE_CREATION = "/v2/sessions/%s/qr-codes";
private static final String IDENTITY_SESSION_QR_CODE_RETRIEVAL_TEMPLATE = "/v2/qr-codes/%s";

// Share V1
private static final String PROFILE = "/profile/%s?appId=%s";
Expand Down Expand Up @@ -41,6 +42,10 @@ public String createIdentitySessionQrCodePath(String sessionId) {
return format(IDENTITY_SESSION_QR_CODE_CREATION, sessionId);
}

public String createIdentitySessionQrCodeRetrievalPath(String qrCodeId) {
return format(IDENTITY_SESSION_QR_CODE_RETRIEVAL_TEMPLATE, qrCodeId);
}

public String createProfilePath(String appId, String connectToken) {
return format(PROFILE, connectToken, appId);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.yoti.api.client.spi.remote.call.identity;

import static com.yoti.api.client.spi.remote.call.HttpMethod.HTTP_GET;
import static com.yoti.api.client.spi.remote.call.HttpMethod.HTTP_POST;
import static com.yoti.api.client.spi.remote.call.YotiConstants.AUTH_ID_HEADER;
import static com.yoti.api.client.spi.remote.call.YotiConstants.CONTENT_TYPE;
Expand All @@ -15,12 +16,14 @@
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.util.Optional;

import com.yoti.api.client.identity.ShareSession;
import com.yoti.api.client.identity.ShareSessionQrCode;
import com.yoti.api.client.identity.ShareSessionRequest;
import com.yoti.api.client.spi.remote.call.ResourceException;
import com.yoti.api.client.spi.remote.call.SignedRequest;
import com.yoti.api.client.spi.remote.call.SignedRequestBuilder;
import com.yoti.api.client.spi.remote.call.SignedRequestBuilderFactory;
import com.yoti.api.client.spi.remote.call.factory.UnsignedPathFactory;
import com.yoti.json.ResourceMapper;
Expand All @@ -32,6 +35,8 @@ public class DigitalIdentityService {

private static final Logger LOG = LoggerFactory.getLogger(DigitalIdentityService.class);

private static final byte[] EMPTY_JSON = "{}".getBytes(StandardCharsets.UTF_8);

private final UnsignedPathFactory pathFactory;
private final SignedRequestBuilderFactory requestBuilderFactory;

Expand Down Expand Up @@ -60,7 +65,7 @@ public ShareSession createShareSession(String sdkId, KeyPair keyPair, ShareSessi

try {
byte[] payload = ResourceMapper.writeValueAsString(shareSessionRequest);
SignedRequest request = createSignedRequest(sdkId, keyPair, path, payload);
SignedRequest request = createSignedRequest(sdkId, keyPair, path, HTTP_POST, payload);

return request.execute(ShareSession.class);
} catch (IOException ex) {
Expand Down Expand Up @@ -89,7 +94,7 @@ public ShareSessionQrCode createShareQrCode(String sdkId, KeyPair keyPair, Strin
LOG.debug("Requesting Share Session QR code Creation for session ID '{}' at '{}'", sessionId, path);

try {
SignedRequest request = createSignedRequest(sdkId, keyPair, path, "{}".getBytes(StandardCharsets.UTF_8));
SignedRequest request = createSignedRequest(sdkId, keyPair, path, HTTP_POST, EMPTY_JSON);

return request.execute(ShareSessionQrCode.class);
} catch (GeneralSecurityException ex) {
Expand All @@ -99,25 +104,50 @@ public ShareSessionQrCode createShareQrCode(String sdkId, KeyPair keyPair, Strin
}
}

public Object fetchShareQrCode(String qrCodeId) {
return null;
public ShareSessionQrCode fetchShareQrCode(String sdkId, KeyPair keyPair, String qrCodeId)
throws DigitalIdentityException {
notNullOrEmpty(sdkId, "SDK ID");
notNull(keyPair, "Application Key Pair");
notNullOrEmpty(qrCodeId, "QR Code ID");

String path = pathFactory.createIdentitySessionQrCodeRetrievalPath(qrCodeId);

LOG.info("Requesting Share Session QR code with ID '{} at '{}'", qrCodeId, path);

try {
SignedRequest request = createSignedRequest(sdkId, keyPair, path);

return request.execute(ShareSessionQrCode.class);
} catch (GeneralSecurityException ex) {
throw new DigitalIdentityException("Error while signing the share QR code fetch request ", ex);
} catch (IOException | URISyntaxException | ResourceException ex) {
throw new DigitalIdentityException("Error while executing the share QR code fetch request ", ex);
}
}

public Object fetchShareReceipt(String receiptId) {
return null;
}

SignedRequest createSignedRequest(String sdkId, KeyPair keyPair, String path, byte[] payload)
SignedRequest createSignedRequest(String sdkId, KeyPair keyPair, String path)
throws GeneralSecurityException, UnsupportedEncodingException, URISyntaxException {
return requestBuilderFactory.create()
return createSignedRequest(sdkId, keyPair, path, HTTP_GET, null);
}

SignedRequest createSignedRequest(String sdkId, KeyPair keyPair, String path, String method, byte[] payload)
throws GeneralSecurityException, UnsupportedEncodingException, URISyntaxException {
SignedRequestBuilder requestBuilder = requestBuilderFactory.create()
.withKeyPair(keyPair)
.withBaseUrl(apiUrl)
.withEndpoint(path)
.withHeader(AUTH_ID_HEADER, sdkId)
.withHttpMethod(HTTP_POST)
.withHeader(CONTENT_TYPE, CONTENT_TYPE_JSON)
.withPayload(payload)
.build();
.withHttpMethod(method);

Optional.ofNullable(payload).map(v ->
requestBuilder.withPayload(v).withHeader(CONTENT_TYPE, CONTENT_TYPE_JSON)
);

return requestBuilder.build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public void builderWithKeyPairSource_NullKeyPairSource_IllegalArgumentException(

assertThat(ex.getMessage(), containsString("Key Pair Source"));
}

@Test
public void build_MissingSdkId_IllegalArgumentException() {
DigitalIdentityClient.Builder builder = DigitalIdentityClient.builder().withKeyPairSource(validKeyPairSource);
Expand Down Expand Up @@ -177,15 +178,18 @@ public void client_CreateShareQrCodeException_DigitalIdentityException() throws
}

@Test
public void client_FetchShareQrCodeException_DigitalIdentityException() {
public void client_FetchShareQrCodeException_DigitalIdentityException() throws IOException {
when(keyPairSource.getFromStream(any(KeyStreamVisitor.class))).thenReturn(keyPair);

DigitalIdentityClient identityClient = new DigitalIdentityClient(
AN_SDK_ID,
validKeyPairSource,
keyPairSource,
identityService
);

String exMessage = "Fetch Share QR Error";
when(identityService.fetchShareQrCode(A_QR_CODE_ID)).thenThrow(new DigitalIdentityException(exMessage));
when(identityService.fetchShareQrCode(AN_SDK_ID, keyPair, A_QR_CODE_ID))
.thenThrow(new DigitalIdentityException(exMessage));

DigitalIdentityException ex = assertThrows(
DigitalIdentityException.class,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ public class DigitalIdentityServiceTest {

private static final String SDK_ID = "anSdkId";
private static final String SESSION_ID = "aSessionId";
private static final String QR_CODE_ID = "aQrCodeId";
private static final String SESSION_CREATION_PATH = "aSessionCreationPath";
private static final String POST = "POST";
private static final byte[] A_BODY_BYTES = "aBody".getBytes(StandardCharsets.UTF_8);

@Spy @InjectMocks DigitalIdentityService identityService;
Expand Down Expand Up @@ -111,15 +113,14 @@ public void createShareSession_SerializingWrongPayload_Exception() {
}

@Test
public void createShareSession_BuildingRequestWithWrongUri_Exception()
throws GeneralSecurityException, UnsupportedEncodingException, URISyntaxException {
public void createShareSession_BuildingRequestWithWrongUri_Exception() throws Exception {
try (MockedStatic<ResourceMapper> mapper = Mockito.mockStatic(ResourceMapper.class)) {
mapper.when(() -> ResourceMapper.writeValueAsString(shareSessionRequest)).thenReturn(A_BODY_BYTES);
when(requestBuilderFactory.create()).thenReturn(signedRequestBuilder);

String exMessage = "URI wrong format";
URISyntaxException causeEx = new URISyntaxException("", exMessage);
when(identityService.createSignedRequest(SDK_ID, keyPair, SESSION_CREATION_PATH, A_BODY_BYTES))
when(identityService.createSignedRequest(SDK_ID, keyPair, SESSION_CREATION_PATH, POST, A_BODY_BYTES))
.thenThrow(causeEx);

DigitalIdentityException ex = assertThrows(
Expand All @@ -142,7 +143,7 @@ public void createShareSession_BuildingRequestWithWrongQueryParams_Exception() t

String exMessage = "Wrong query params format";
UnsupportedEncodingException causeEx = new UnsupportedEncodingException(exMessage);
when(identityService.createSignedRequest(SDK_ID, keyPair, SESSION_CREATION_PATH, A_BODY_BYTES))
when(identityService.createSignedRequest(SDK_ID, keyPair, SESSION_CREATION_PATH, POST, A_BODY_BYTES))
.thenThrow(causeEx);

DigitalIdentityException ex = assertThrows(
Expand All @@ -165,7 +166,7 @@ public void createShareSession_BuildingRequestWithWrongDigest_Exception() throws

String exMessage = "Wrong digest";
GeneralSecurityException causeEx = new GeneralSecurityException(exMessage);
when(identityService.createSignedRequest(SDK_ID, keyPair, SESSION_CREATION_PATH, A_BODY_BYTES))
when(identityService.createSignedRequest(SDK_ID, keyPair, SESSION_CREATION_PATH, POST, A_BODY_BYTES))
.thenThrow(causeEx);

DigitalIdentityException ex = assertThrows(
Expand All @@ -186,7 +187,7 @@ public void createShareSession_SessionRequest_exception() throws Exception {

when(requestBuilderFactory.create()).thenReturn(signedRequestBuilder);

when(identityService.createSignedRequest(SDK_ID, keyPair, SESSION_CREATION_PATH, A_BODY_BYTES))
when(identityService.createSignedRequest(SDK_ID, keyPair, SESSION_CREATION_PATH, POST, A_BODY_BYTES))
.thenReturn(signedRequest);

when(signedRequest.execute(ShareSession.class)).thenReturn(shareSession);
Expand Down Expand Up @@ -236,6 +237,44 @@ public void createShareQrCode_NullSessionId_Exception() {
assertThat(ex.getMessage(), containsString("Session ID"));
}

@Test
public void fetchShareQrCode_NullSdkId_Exception() {
IllegalArgumentException ex = assertThrows(
IllegalArgumentException.class,
() -> identityService.fetchShareQrCode(null, keyPair, QR_CODE_ID)
);

assertThat(ex.getMessage(), containsString("SDK ID"));
}

@Test
public void fetchShareQrCode_EmptySdkId_Exception() {
IllegalArgumentException ex = assertThrows(
IllegalArgumentException.class,
() -> identityService.fetchShareQrCode("", keyPair, QR_CODE_ID)
);

assertThat(ex.getMessage(), containsString("SDK ID"));
}

@Test
public void fetchShareQrCode_NullKeyPair_Exception() {
IllegalArgumentException ex = assertThrows(
IllegalArgumentException.class,
() -> identityService.fetchShareQrCode(SDK_ID, null, QR_CODE_ID)
);

assertThat(ex.getMessage(), containsString("Application Key Pair"));
}

@Test
public void fetchShareQrCode_NullSessionId_Exception() {
IllegalArgumentException ex = assertThrows(
IllegalArgumentException.class,
() -> identityService.fetchShareQrCode(SDK_ID, keyPair, null)
);

assertThat(ex.getMessage(), containsString("QR Code ID"));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -71,18 +71,32 @@ public String identityShare(Model model) throws URISyntaxException {

String sessionId = session.getId();

model.addAttribute("session_id", sessionId);
model.addAttribute("session_status", session.getStatus());
model.addAttribute("session_expiry", session.getExpiry());

ShareSessionQrCode sessionQrCode = execute(() -> client.createShareQrCode(sessionId), model);
if (sessionQrCode == null) {
return "error";
}

model.addAttribute("session_id", sessionId);
model.addAttribute("session_status", session.getStatus());
model.addAttribute("session_expiry", session.getExpiry());
String qrCodeId = sessionQrCode.getId();

model.addAttribute("session_qrcode_id", sessionQrCode.getId());
model.addAttribute("session_qrcode_id", qrCodeId);
model.addAttribute("session_qrcode_uri", sessionQrCode.getUri());

ShareSessionQrCode fetchQrCode = execute(() -> client.fetchShareQrCode(qrCodeId), model);
if (fetchQrCode == null) {
return "error";
}

model.addAttribute("qrcode_expiry", fetchQrCode.getExpiry());
model.addAttribute("qrcode_extensions", fetchQrCode.getExtensions());
model.addAttribute("qrcode_redirect_uri", fetchQrCode.getRedirectUri());
model.addAttribute("qrcode_session_id", fetchQrCode.getSession().getId());
model.addAttribute("qrcode_session_status", fetchQrCode.getSession().getStatus());
model.addAttribute("qrcode_session_expiry", fetchQrCode.getSession().getExpiry());

return "digital-identity-share";
}

Expand Down
Loading

0 comments on commit b45d59a

Please sign in to comment.