CSJCurrent en:Migration from Java API v3 to v5.0

Aus Cryptshare Documentation
Version vom 1. September 2022, 08:08 Uhr von Erhardts (Diskussion | Beiträge) (Removed top hr)
(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)
Wechseln zu:Navigation, Suche

Introduction

In this article, you will learn how to migrate an existing project that uses the Cryptshare Java API version 3 to the latest version of the Cryptshare Java API. This article is a technical article that lists the changes for people who are familiar with using the previous Java API version. For general information about the changes introduced in version 5, see the update notes.

Preparation

Installing the new Java API Version

For information on installing the new Java API version into your development system, click here for more information.

Migrating existing projects

Replace dependency in the dependency management system

The GroupID and the ArtifactID of the Java API have changed. First, please replace the dependency entry in your dependency management, e.g. in the pom.xml as follows:

Old

<dependency>
   <groupId>com.befinesolutions.cryptshare</groupId> 
   <artifactId>java-api-ng</artifactId> 
   <version>3.X.X</version>
</dependency>
New

<dependency>
	<groupId>com.cryptshare.api</groupId>
	<artifactId>cryptshare-api</artifactId>
	<version>5.0.1.190</version>
</dependency>

Incompatibility with existing client.store

The structure of the `client.store` file has been fundamentally revised. Thus, it is unfortunately not possible to continue using the existing `client.store` together with its verifications. Before you test your project with the new version of the Java API, delete the existing `client.store` file first.

com.cryptshare.api.ClientException: Can't read from the client store!
	at com.cryptshare.api.Client.openStore(Client.java:810)
	at com.cryptshare.api.Client.<init>(Client.java:126)
	at com.cryptshare.api.Client.<init>(Client.java:86)
	at com.befinesolutions.examples.api.ExampleMain.connectWithStore(ExampleMain.java:351)
	at com.befinesolutions.examples.api.ExampleMain.main(ExampleMain.java:48)
Caused by: com.cryptshare.api.ClientException: Unknown error
	at com.cryptshare.api.DpApiJNAWrapper.unprotect(DpApiJNAWrapper.java:57)
	at com.cryptshare.api.DpApiProtectionService.unprotect(DpApiProtectionService.java:74)
	at com.cryptshare.api.ProtectedFileStore.load(ProtectedFileStore.java:130)
	at com.cryptshare.api.ProtectedFileStore.open(ProtectedFileStore.java:108)
	at com.cryptshare.api.ProtectedFileStore.open(ProtectedFileStore.java:83)
	at com.cryptshare.api.Client.openStore(Client.java:808)
	... 4 more
Caused by: com.sun.jna.platform.win32.Win32Exception: Wrong parameter.
	at com.sun.jna.platform.win32.Crypt32Util.cryptUnprotectData(Crypt32Util.java:144)
	at com.cryptshare.api.DpApiJNAWrapper.unprotect(DpApiJNAWrapper.java:55)
	... 9 more

Changed package name

The package name of the used classes has changed. Next, in the import statements of your classes, please replace the package name:

Old

com.befinesolutions.cryptshare.aping
New

com.cryptshare.api

Changes in exception handling

To better identify the source of possible problems when using the API, CryptshareServerExceptions are now thrown instead of ClientExceptions in some cases. A ClientException is usually thrown in cases when an operation failed during preparation. CryptshareServerExceptions occur when the operation itself has failed on the Cryptshare server, when incorrect information has been detected, or processing or other errors have occurred.

Changes in the verification process

Initializing a Client

When creating a `Client` object, a string containing the path to the `client.store` file was previously required. Instead, a `Path` object containing the path to the client.store is now required, e.g. by `Paths.get(stringPath)`.

Using a byte array as client store

In case a byte array was used instead of a `client.store` file, a custom implementation of the new interface `IStore` must now be used. You can find more information in the article Provide your own Client Store.

Example implementation

Java API 3

// Get client store from your custom store.
byte[] clientStoreBytes = // TODO: Load bytes from your custom store

CryptshareConnection connection = new CryptshareConnection(new WebServiceUri(SERVER_URL))
Client client = new Client("sender@domain.com", connection, clientStoreBytes);

// do something like verifications, transfers, ...

// Write encrypted modified client store back into the custom store.
byte[] modifiedClientStoreBytes = client.getStore().save();
// TODO: Save modifiedClientStoreBytes to your custom store

The possibility to specify a byte array when creating a client object has been removed. Instead, an implementation of the IStore interface is required. In the following example a minimal implementation has been implemented. Please note the places marked with TODO. These places indicate places where you would need to check if your operating system used is the same as the example or where you need to implement your own load and store methods that you have already used so far in your program using the Cryptshare Java API v3.

Java API 5 CustomStore

import com.cryptshare.api.*;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * Implementation of {@link IStore} that persists to a custom source, and encrypts the store.
 */
public class CustomProtectedStore implements IStore {

	private final Map<String, String> storeValues;
	private final IProtectionService protectionService;

	private CustomProtectedStore(IProtectionService protectionService) {
		this.protectionService = protectionService;
		this.storeValues = new HashMap<>();
	}

	/**
	 * A default {@link IProtectionService} is used to protect the store:
	 * AES encryption, using {@link AesProtectionService} with {@link LinuxKeySourceProvider}.
	 *
	 * @return An instance of a {@link CustomProtectedStore}, with all values loaded.
	 * @throws ClientException If opening fails.
	 */
	public static CustomProtectedStore open() throws ClientException {
		// TODO: When your program is run under Windows instead, use the DpApiProtectionService instead of the {@link AesProtectionService}.
		IProtectionService protectionService = new AesProtectionService(new LinuxKeySourceProvider());
		CustomProtectedStore protectedFileStore = new CustomProtectedStore(protectionService);
		protectedFileStore.load();

		return protectedFileStore;
	}

	/**
	 * Read from client store
	 */
	private void load() throws ClientException {
		try {
			byte[] protectedContent = loadStore();
			if (protectedContent.length == 0) {
				return;
			}

			byte[] unprotectedContent = protectionService.unprotect(protectedContent);
			String storeContent = new String(unprotectedContent, StandardCharsets.UTF_8);

			String[] entries = storeContent.split(";");

			for (String entry : entries) {
				String key = entry.substring(0, entry.indexOf('='));
				String value = entry.substring(entry.indexOf('=') + 1);
				storeValues.put(new String(Hex.decodeHex(key)), value);
			}

		} catch (DecoderException e) {
			throw new ClientException(ClientException.CODE_STORE_ACCESS_FAILED, "Could not read from the client store!", e);
		}
	}

	/**
	 * Gets a stored value from the store.
	 *
	 * @param key Unique key to identify the value. If null, the method returns null.
	 * @return The value that corresponds to the given key, or null, if the key was null or not found.
	 */
	@Override
	public synchronized String getValue(String key) {
		if (key == null) {
			return null;
		}
		return storeValues.get(key);
	}

	/**
	 * Sets a value in the store.
	 *
	 * @param key   Unique key for the value. If null, the method call has no effect.
	 * @param value Value to be stored. If null, the method call has no effect.
	 */
	@Override
	public synchronized void setValue(String key, String value) {
		if (key == null || value == null) {
			return;
		}
		storeValues.put(key, value);
	}

	/**
	 * Removes a specific value from the store.
	 *
	 * @param key Unique key to identify the value. If null, the method call has no effect.
	 */
	@Override
	public synchronized void removeValue(String key) {
		if (key == null) {
			return;
		}
		storeValues.remove(key);
	}

	/**
	 * Saves this store object to the custom store.
	 */
	@Override
	public synchronized void persist() throws ClientException {
		StringBuilder content = new StringBuilder();
		storeValues.forEach((k, v) -> {
			content.append(Hex.encodeHexString(k.getBytes()));
			content.append("=");
			content.append(v);
			content.append(";");
		});

		byte[] unprotectedContent = content.toString().getBytes(StandardCharsets.UTF_8);
		byte[] protectedContent = protectionService.protect(unprotectedContent);

		saveStore(protectedContent);
	}

	private byte[] loadStore() {
		// TODO: Load bytes from your custom store, e.g. from a file or database.
	}

	private void saveStore(byte[] bytes) {
		// TODO: Save bytes to your custom store, e.g. to a file or database.
	}
}
Java API 5

// Get client store from your custom store.
CryptshareConnection connection = new CryptshareConnection(new WebServiceUri(SERVER_URL))
Client client = new Client("sender@domain.com", Locale.ENGLISH, connection, CustomProtectedStore.open());

// do something like verifications, transfers, ...

// Write encrypted modified client store back into the custom store.
// Info: This step is not necessary anymore.

Verification

The check for the current verification status has changed. The `Client#checkVerification` method now returns a `CheckVerificationResult` object. Whether the client is verified is now checked with `CheckVerificationResult#isUserVerified` instead of `VerificationStatus#isVerified`. The verification mode is obtained with `CheckVerificationResult#getVerificationMode` instead of `VerificationStatus#isSenderVerification`. Below you can see the verification process in detail for both verification modes:

Client Verification

private final String SENDER = "john.doe@example.com";
private final String SERVER_URL = "https://cryptshare.mydomain.com";
private final String CLIENT_STORE_PATH = "client.store";

CryptshareConnection connection = new CryptshareConnection(new WebServiceUri(SERVER_URL));
Client client = new Client(SENDER, connection, Paths.get(CLIENT_STORE_PATH));
System.out.println("Checking whether the client is verified...");
CheckVerificationResult result = client.checkVerification();
if (result.isUserVerified()) {
	System.out.println("The client is verified; nothing to do.");
} else {
	if (result.getVerificationMode() == VerificationMode.SENDER) {
		System.out.println("Verification mode is set to 'Sender', please set it to 'Client' and add the following Client ID: " + client.getClientId());
    	return;
	}
	try {
		client.RequestClientVerification();
		System.out.println("The client verification has been requested. Please rerun the program and check whether your client is verified.");
	} catch (CryptshareServerException e) {
		System.out.println("The verification mode is set to 'Client', but your Client ID is not whitelisted. " +
                     	   "Please add your Client ID '" + client.getClientId() + "' to the list of allowed clients.");
		return;
	}
}

The method `Client#checkVerification` now returns another type of object that also has to be used differently for checking for an existing verification. Instead of calling `Client#requestVerification` a client verification has to be requested explicitely by `Client#requestClientVerification`. In case the client is not verified yet `Client#requestClientVerification` throws an exception that has to be handled.

Sender Verification

private final String SENDER = "john.doe@example.com";
private final String SERVER_URL = "https://cryptshare.mydomain.com";
private final String CLIENT_STORE_PATH = "client.store";

CryptshareConnection connection = new CryptshareConnection(new WebServiceUri(SERVER_URL));
Client client = new Client(SENDER, connection, Paths.get(CLIENT_STORE_PATH));
System.out.println("Checking whether the client is verified...");
CheckVerificationResult result = client.checkVerification();
if (result.isUserVerified()) {
	System.out.println("The client is verified; nothing to do.");
} else {
	if (result.getVerificationMode() == VerificationMode.CLIENT) {
		System.out.println("Verification mode is set to 'Client', please set it to 'Sender'.");
    	return;
	}
	client.RequestSenderVerification();
	System.out.println("Please type in the verification code:");
	BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
	String vericode = reader.readLine();
	reader.close();

	// now tell the client to store the verification code in the verification store
	client.confirmSenderVerification(vericode.trim());
}

The method `Client#checkVerification` now returns another type of object that also has to be used differently for checking for an existing verification. Instead of calling `Client#requestVerification` a sender verification has to be requested explicitely by `Client#requestSenderVerification`. Saving the verification code does not work via `Client#storeVerification`, but with the method `Client#confirmSenderVerification`.

Changed transfer process

Preparing a transfer

Methods for preparing a transfer were renamed or require new data types as parameters.:

Description Old Method New Method Change
Should the server send a download notification to the sender of the transfer? Transfer#setDownloadNotification Transfer#setInformAboutDownload New method name
Setting the language for sender notification emails Transfer#setSenderLanguage Client#setUserLanguage New method name and the language is set for the client instead of the transfer.
Setting transfer recipients Transfer#addRecipient

Transfer#setRecipients Transfer#addRecipients

These methods now require `Recipient` objects instead of strings, e.g.

`transfer.addRecipient("john.doe@cryptshare.com")` will be replaced by `transfer.addRecipient(new Recipient("john.doe@cryptshare,com"))`

Setting the expiration date Transfer#setExpirationDate(Date) Transfer#setExpirationDate(LocalDate) This method now requires a LocalDate object instead of a Date object, e.g.

LocalDate.of(2020, 12, 24);

Setting the recipient language Transfer#setRecipientLanguage(String) Transfer#setRecipientLanguage(Locale) This method now requires a Locale object instead of a string, e.g. Locale.ENGLISH.
Generate a random password Client#requestPassword(int)

Client#requestPassword(int, boolean)

Client#requestGeneratedPassword(int) New method name.

The local generation of passwords is not possible anymore.

Setting a generated password Transfer#setGeneratedPassword Transfer#setPassword Automatically generated passwords have to set exactly as manual passwords.
Creating objects for transfer files TransferFile(String) TransferFile(Path) The creation of TransferFile objects now require a Path object instead of a string, e.g. Paths.get(pathString).
Adding transfer files to a transfer Transfer#setFiles Transfer#setTransferFiles New method name

Perform a transfer

The execution of asynchronous and synchronous transfers has been adjusted by not requiring only one class that takes care of all possible state changes of the transfer such as upload progress change, upload finished, upload interrupted and transfer aborted. Instead, a separate handler can now be defined for each of these state changes. These derive their names from the following interfaces: IUploadProgressChangedHandler, IUploadCompleteHandler, IUploadInterruptedHandler, IUploadCancelledHandler. These each require a method to be implemented. The method to be implemented directly provides all available information, so it is no longer necessary to extract it from an event object. If a handler is not needed, null can be specified for that handler when starting a transfer. Examples of handlers:

public class UploadProgressChangedHandler implements IUploadProgressChangedHandler {
	@Override
	public void progressChanged(String currentFileNo, String currentFileName, double bytesUploaded, double bytesTotal, long bytesPerSecond) {
		// do something
	}
}

public class UploadCompleteHandler implements IUploadCompleteHandler {
	@Override
	public void uploadComplete(Map<String, String> urlMappings, Map<String, String> smtpMappings, String serverGenPassword, TransferError transferError,
			String trackingId) {
		// do something
	}
}

public class UploadInterruptedHandler implements IUploadInterruptedHandler {
	@Override
	public void uploadInterrupted(CryptshareException cryptshareException) {
		// do something
	}
}

public class UploadCancelledHeader implements IUploadCancelledHandler {
	@Override
	public void cancelled() {
		// do something
	}
}

Usage of handler during starting a transfer:

// Asynchronous Transfer
client.beginTransfer(transfer, new UploadProgressChangedHandler(), new UploadCompleteHandler(), new UploadInterruptedHandler(), new UploadCancelledHeader(), pollingTimer);

// Synchronous Transfer
client.performTransfer(transfer, new UploadProgressChangedHandler(), new UploadCompleteHandler(), new UploadInterruptedHandler(), new UploadCancelledHeader(), pollingTimer);

The new integer parameter pollingTimer specifies in milliseconds how often the server should be polled for the current state of the running transfer, e.g. every 1000 milliseconds (= 1 second).

Changes in other methods

Requesting general server data

Client#requestTransferData was removed. Please use Client#requestPolicy instead to determine the valid settings for your sender-recipient combination.

List<String> recipients = new ArrayList<>();
recipients.add("user001@cryptshare.com");
recipients.add("user002@cryptshare.com");
Policy policy = client.requestPolicy(recipients);

Policy rules

Names and data types of some policy information fields have changed:

Description Old Method New Method Change
Allowed standard password modes Policy#getPasswordMode Policy#getAllowedStandardPasswordModes New method name and changed data type to `Set` instead of a `List`.
Recipients that are now allowed for the given sender Policy#getFailedAddresses Policy#getFailedEmailAddresses New method name and changed data type to `Set` instead of a `List`.

Language Resources

Retrieving defined language pack versions is no longer possible. Instead, use those methods that do not specify the language package version, e.g. Client#requestLanguagePacks() and Client#requestLanguagePackFile(String, Locale).

Description Old Method New Method Change
Determine the language pack version LanguagePack#getLanguagePackVersion LanguagePack#getVersion New method name.
Determine the last change for this language pack LanguagePack#getLastUpdate Method now returns a ZoneDateTime object instead of a Unix timestamp in the Long format.

Terms of Use

The return type of the terms of use using Client#requestTermsOfUse has changed. This method now returns a TermsOfUse object. You can get the terms of use specific to a language via termsOfUse#getTextByLanguage(Locale).

Licence Information

The data type of individual fields within the LicenseInfo object queried via Client#requestLicenseInfo have changed from String to LocalDate: LicenseInfo#getProductLicenseExpirationDate, LicenseInfo#getProductSubscriptionExpirationDate, LicenseInfo#getServerLicenseExpirationDate, and LicenseInfo#getServerSubscriptionExpirationDate.

Requesting email templates from the Cryptshare server 

The method Client#requestMailTemplate(String, Map<Set<String>, Set<TemplateReplacement>, <language>, <mailFormat>>, Locale, String) now requires another new last parameter of type EmailFormat. Example:

Old

Map<List<String>, String> mailTemplates = client.requestMailTemplate("recipient", replacements, new Locale("ja"), "html");
New

Map<List<String>, String> mailTemplates = client.requestMailTemplate("recipient", replacements, new Locale("ja"), EmailFormat.HTML);

Additionally, the enumeration TemplatePlaceholder was removed. In order to replace the TemplatePlaceholders with their respective strings, consult the article Email Notifications.

Old

Set<TemplateReplacement> replacementSet = new HashSet<TemplateReplacement>();
replacementSet.add(new TemplateReplacement(TemplatePlaceholder.NAME, "John Adams"));
replacementSet.add(new TemplateReplacement(TemplatePlaceholder.EMAIL, "john.adams@server.com"));
New

Set<TemplateReplacement> replacementSet = new HashSet<>();
replacementSet.add(new TemplateReplacement("name", "John Adams"));
replacementSet.add(new TemplateReplacement("email", "john.adams@server.com"));

Validating a password

Using the `Client#checkPassword` method, it was previously possible to have the server check the quality of a password. A `PasswordPolicy` object was returned as the return value. This has been replaced by a `PasswordCheckResult` object for this method. Fields not required by this method, which were previously always `null`, have been removed.

Manual setting of transfer errors

When sending email notification was not handled by the Cryptshare server but by the own application, it is possible to tell the Cryptshare server that the sending of emails failed. Using the method Client#updateTransferError(String trackingId, TransferError error) a TransferError can be defined for a specific tracking ID of a transfer. This TransferError has been modified accordingly so that values that can no longer be set cannot be set by setters. Likewise, to set those failed recipients, the recipient list of the TransferError must first be retrieved.

Old

TransferError error = new TransferError();
List<String> recipients = new ArrayList<>();
recipients.add("recipient@cryptshare.com");
error.setFailedRecipients(recipients);
New

TransferError error = new TransferError();
List<String> recipients = new ArrayList<>();
recipients.add("recipient@cryptshare.com");
error.getFailedRecipients().addAll(recipients);

Removed methods

  • Transfer#setSessionID
  • Transfer#setTransferSize
  • Client#manageTransfer
  • Client#handleAsynchFileTransferCompleted