Skip to content

Commit

Permalink
#297: Improved the new Recipients API. S/MIME encryption now on both …
Browse files Browse the repository at this point in the history
…Recipient and Recipients level on top of Email and Mailer level. Updated both Recipient and Recipients APIs to include S/MIME encryption as a feature. This adds to the existing levels for S/MIME encryption which were Email and Mailer. The changes ensure a flexible, layered approach to managing S/MIME encryption, offering specific controls at each level. Order of precedence has now been established to clarify encryption priority.
  • Loading branch information
bbottema committed Mar 25, 2024
1 parent 4f20ae3 commit 2a10531
Show file tree
Hide file tree
Showing 22 changed files with 504 additions and 373 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,21 @@
import java.security.cert.X509Certificate;

/**
* Produces immutable recipient object, with a name, emailaddress and recipient type (eg {@link Message.RecipientType#BCC}),
* and optionally an S/MIME certificate for encrypting messages on a per-user basis.
* Produces immutable recipient object, with a name, emailaddress and recipient type (eg {@link Message.RecipientType#BCC}), and optionally an S/MIME
* certificate for encrypting messages on a per-user basis.
*/
public interface IRecipientBuilder {

/**
* @param name Optional explicit name of the recipient, otherwise taken from inside the address (if provided) (for example "Joe Sixpack <[email protected]>").
* @see #clearingName()
* @param name Optional explicit name of the recipient, otherwise taken from inside the address (if provided, for example "Joe Sixpack
* <[email protected]>"). Note that in {@link Recipients}, this can still be overridden by the {@code defaultName} and
* {@code overridingName} fields.
*/
IRecipientBuilder withName(@NotNull String name);

/**
* Clears the name, in which case the name from inside the provided address is used (if provided), or else the address is used as-is.
* So in email clients you won't see a name, just the address.
*
* @see #withName(String)
*/
IRecipientBuilder clearingName();

/**
* @param address The email address of the recipient, can contain a name, but is ignored if a name was seperately provided.
* @param address The email address of the recipient, can contain a name, but is ignored if a name was seperately provided, this includes names possibly
* provided by {@link Recipients}.
*/
IRecipientBuilder withAddress(@NotNull String address);

Expand All @@ -37,20 +31,33 @@ public interface IRecipientBuilder {
IRecipientBuilder withType(@NotNull Message.RecipientType type);

/**
* @param smimeCertificate Optional S/MIME certificate for this recipient, used for encrypting messages on a per-user basis. Overrides certificate provided
* on {@link Email} level and {@link org.simplejavamail.api.mailer.Mailer} level (if provided).
* @param smimeCertificate Optional S/MIME certificate for this recipient, used for encrypting S/MIME messages on a per-user basis. Overrides certificate
* provided on {@link Email} level and {@link org.simplejavamail.api.mailer.Mailer} level (if provided).
* <p></p>
* So, the order of precedence is:
* <ol>
* <li>Mailer level (override value)</li>
* <li>Recipient level (specific value)</li>
* <li>Recipients level (default value)</li>
* <li>Email level (default value)</li>
* <li>Mailer level (default value)</li>
* </ol>
* @see #clearingSmimeCertificate()
* @see IRecipientsBuilder#withDefaultSmimeCertificate(X509Certificate)
*/
IRecipientBuilder withSmimeCertificate(@NotNull X509Certificate smimeCertificate);

/**
* Clears the S/MIME certificate used for encrypting S/MIME messages for this recipient. In this case, if available, the S/MIME certificate from
* the {@link Email} object is used and from the {@link org.simplejavamail.api.mailer.Mailer} otherwise (if provided).
* Clears the S/MIME certificate used for encrypting S/MIME messages for this recipient. In this case, if available, the S/MIME certificate from the
* {@link Email} object is used and from the {@link org.simplejavamail.api.mailer.Mailer} otherwise (if provided).
*
* @see #withSmimeCertificate(X509Certificate)
*/
IRecipientBuilder clearingSmimeCertificate();

/**
* Creates a new {@link Recipient} instance, but first checks if address is set and throws an exception if not.
*/
@NotNull Recipient build();

/**
Expand All @@ -72,4 +79,4 @@ public interface IRecipientBuilder {
* @see #withSmimeCertificate(X509Certificate)
*/
@Nullable X509Certificate getSmimeCertificate();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package org.simplejavamail.api.email;

import jakarta.mail.Message;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.security.cert.X509Certificate;
import java.util.List;

/**
* Produces immutable recipients object, with an optional default/overriding name.
*/
public interface IRecipientsBuilder {

/**
* Default explicit name of the recipient, otherwise taken from inside the {@link Recipient}'s explicitly set name, or otherwise from the nested address (if
* provided).
* <p>
* The reason for this option is due to the natur of an email address on the string level, where we don't always control the value of the name part of the
* address. This is especially true when the address is provided by a user, where the name part can be anything, including a name that is not a name at all,
* or perhaps previously set defaults in a properties configuration.
* </p>
*/
IRecipientsBuilder withDefaultName(@Nullable String defaultName);

/**
* Similar to {@link #withDefaultName(String)}, but this field is used to override the name of all recipients in this object. The nested name of each
* recipient is simply ignored if this field is set, including explicitly set names or implicitly derived names from the address.
*/
IRecipientsBuilder withOverridingName(@Nullable String overridingName);

IRecipientsBuilder withRecipient(@NotNull Recipient recipient);

IRecipientsBuilder withRecipients(@NotNull Recipient... recipient);

/**
* @param defaultSmimeCertificate Optional S/MIME certificate for all recipients in this object, used for encrypting messages on a per-user basis. Overrides
* certificate provided on {@link Email} level and {@link org.simplejavamail.api.mailer.Mailer} level (if provided). If
* overridden by a specific recipient, that recipient's certificate is used instead. The only exception is when a
* {@code Mailer} level override is set, in which case that is used for all recipients.
* <p></p>
* So, the order of precedence is:
* <ol>
* <li>Mailer level (override value)</li>
* <li>Recipient level (specific value)</li>
* <li>Recipients level (default value)</li>
* <li>Email level (default value)</li>
* <li>Mailer level (default value)</li>
* </ol>
* @see IRecipientBuilder#withSmimeCertificate(X509Certificate)
*/
IRecipientsBuilder withDefaultSmimeCertificate(@NotNull X509Certificate defaultSmimeCertificate);

/**
* @return An immutable {@link Recipients} object, with an optional default/overriding name and an optional default S/MIME certificate.
*/
@NotNull Recipients build();

/**
* @see #withDefaultName(String)
*/
@Nullable String getDefaultName();

/**
* @see #withOverridingName(String)
*/
@Nullable String getOverridingName();

/**
* @see #withRecipient(Recipient)
* @see #withRecipients(Recipient...)
*/
@NotNull List<Recipient> getRecipients();

/**
* @see #withDefaultSmimeCertificate(X509Certificate)
*/
@Nullable X509Certificate getDefaultSmimeCertificate();
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,112 +2,41 @@

import jakarta.mail.Message;
import jakarta.mail.Message.RecipientType;
import lombok.Value;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.Serializable;
import java.security.cert.X509Certificate;
import java.util.Objects;

import static org.simplejavamail.internal.util.Preconditions.checkNonEmptyArgument;

/**
* An immutable recipient object, with a name, emailaddress and recipient type (eg {@link Message.RecipientType#BCC}),
* and optionally an S/MIME certificate for encrypting messages on a per-user basis.
* An immutable recipient object, with a name, emailaddress and recipient type (eg {@link Message.RecipientType#BCC}), and optionally an S/MIME certificate for
* encrypting messages on a per-user basis.
*
* @see IRecipientBuilder
*/
public final class Recipient implements Serializable {

private static final long serialVersionUID = 1234567L;

@Nullable
private final String name;
@NotNull
private final String address;
@Nullable
private final RecipientType type;
@Nullable
private final X509Certificate smimeCertificate;

/**
* Constructor; initializes this recipient object.
*
* @param name Optional explicit name of the recipient, otherwise taken from inside the address (if provided) (for example "Joe Sixpack &lt;[email protected]&gt;").
* @param address The email address of the recipient, can contain a name, but is ignored if a name was seperately provided.
* @param type The recipient type (e.g. {@link RecipientType#TO}), optional for {@code from} and {@code replyTo} fields.
* @param smimeCertificate Optional S/MIME certificate for this recipient, used for encrypting messages on a per-user basis. Overrides certificate provided
* on {@link Email} level and {@link org.simplejavamail.api.mailer.Mailer} level (if provided).
* @see IRecipientBuilder
*/
public Recipient(@Nullable final String name, @NotNull final String address, @Nullable final RecipientType type, @Nullable final X509Certificate smimeCertificate) {
this.name = name;
this.address = checkNonEmptyArgument(address, "address");
this.type = type;
this.smimeCertificate = smimeCertificate;
}

/**
* Checks equality based on {@link #name}, {@link #address} and {@link #type}.
*/
@Override
public boolean equals(@Nullable final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final Recipient recipient = (Recipient) o;
return Objects.equals(name, recipient.name) &&
Objects.equals(address, recipient.address) &&
Objects.equals(type, recipient.type);
}

@Override
public int hashCode() {
return Objects.hash(name, address, type);
}

@NotNull
@Override public String toString() {
return "Recipient{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
", type=" + type +
", smimeCertificate=" + smimeCertificate +
'}';
}

/**
* @see IRecipientBuilder#withName(String)
*/
@Nullable
public String getName() {
return name;
}

/**
* @see IRecipientBuilder#withAddress(String)
*/
@NotNull
public String getAddress() {
return address;
}

/**
* @see IRecipientBuilder#withType(RecipientType)
*/
@Nullable
public RecipientType getType() {
return type;
}

/**
* @see IRecipientBuilder#withSmimeCertificate(X509Certificate)
*/
@Nullable
public X509Certificate getSmimeCertificate() {
return smimeCertificate;
}
@Value
public class Recipient implements Serializable {

private static final long serialVersionUID = 1234567L;

/**
* @see IRecipientBuilder#withName(String)
*/
@Nullable String name;

/**
* @see IRecipientBuilder#withAddress(String)
*/
@NotNull String address;

/**
* @see IRecipientBuilder#withType(RecipientType)
*/
@Nullable RecipientType type;

/**
* @see IRecipientBuilder#withSmimeCertificate(X509Certificate)
*/
@Nullable X509Certificate smimeCertificate;
}
Original file line number Diff line number Diff line change
@@ -1,41 +1,41 @@
package org.simplejavamail.api.email;

import jakarta.mail.Message.RecipientType;
import lombok.Value;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.security.cert.X509Certificate;
import java.util.List;

/**
* An immutable recipient object, with a name, emailaddress and recipient type (eg {@link RecipientType#BCC}),
* and optionally an S/MIME certificate for encrypting messages on a per-user basis.
* An immutable recipients object, with an optional default or overriding name.
*
* @see IRecipientBuilder
* @see IRecipientsBuilder
*/
public final class Recipients implements Serializable {

private static final long serialVersionUID = 1234567L;

@NotNull
private final List<Recipient> recipients = new ArrayList<>();
@Nullable
private final String defaultName;
@Nullable
private final String overridingName;

/**
* Constructor; initializes this recipient object.
*
* @param defaultName Optional explicit name of the recipient, otherwise taken from inside the address (if provided) (for example "Joe Sixpack &lt;
* @param overridingName The email address of the recipient, can contain a name, but is ignored if a name was seperately provided.
* @see IRecipientBuilder
*/
public Recipients(@Nullable final String defaultName, @Nullable final String overridingName, @NotNull final Recipient... recipients) {
this.recipients.addAll(Arrays.asList(recipients));
this.defaultName = defaultName;
this.overridingName = overridingName;
}
@Value
public class Recipients implements Serializable {

private static final long serialVersionUID = 1234567L;

/**
* @see IRecipientsBuilder#withRecipient(Recipient)
* @see IRecipientsBuilder#withRecipients(Recipient...)
*/
@NotNull List<Recipient> recipients;

/**
* @see IRecipientsBuilder#withDefaultName(String)
*/
@Nullable String defaultName;

/**
* @see IRecipientsBuilder#withOverridingName(String)
*/
@Nullable String overridingName;

/**
* @see IRecipientsBuilder#withDefaultSmimeCertificate(X509Certificate)
*/
@Nullable X509Certificate defaultSmimeCertificate;
}
Original file line number Diff line number Diff line change
Expand Up @@ -169,12 +169,12 @@ public static Recipient interpretRecipient(@Nullable final String name, boolean
final String relevantName = (fixedName || parsedAddress.getPersonal() == null)
? defaultTo(name, parsedAddress.getPersonal())
: defaultTo(parsedAddress.getPersonal(), name);
return new Recipient(relevantName, parsedAddress.getAddress(), type);
return new Recipient(relevantName, parsedAddress.getAddress(), type, null);
} catch (final AddressException e) {
// InternetAddress failed to parse the email address even in non-strict mode
// just assume the address was too complex rather than plain wrong, and let our own email validation
// library take care of it when sending the email
return new Recipient(name, emailAddress, type);
return new Recipient(name, emailAddress, type, null);
}
}

Expand Down
Loading

0 comments on commit 2a10531

Please sign in to comment.