branch develop updated (dccc39f7 -> 772268b1)
This is an automated email from the git hooks/post-receive script. New change to branch develop in repository pollen. See https://gitlab.nuiton.org/chorem/pollen.git from dccc39f7 refs #208 : Jugement majoritaire : ne plus modifer les mention après le premier vote new 772268b1 refs #79 : envoie de mail chiffré The 1 revisions listed above as "new" are entirely new to this repository and will be described in separate emails. The revisions listed as "adds" were already present in the repository and have only been added to this reference. Detailed log of new commits: commit 772268b108a394c10d8288b790776d2ec134fc02 Author: Sylvain Bavencoff <bavencoff@codelutin.com> Date: Wed May 30 10:39:24 2018 +0200 refs #79 : envoie de mail chiffré Summary of changes: .../h2/V3_2_0_2__add_PGP_public_key_in_email.sql | 2 + .../V3_2_0_2__add_PGP_public_key_in_email.sql | 2 + pollen-persistence/src/main/xmi/pollen.properties | 2 +- pollen-persistence/src/main/xmi/pollen.zargo | Bin 30846 -> 30924 bytes .../rest/api/PollenRestApiRequestFilter.java | 4 +- .../chorem/pollen/rest/api/v1/PollenUserApi.java | 18 ++ pollen-services/pom.xml | 9 + .../services/bean/PollenUserEmailAddressBean.java | 10 ++ .../pollen/services/service/CryptoService.java | 183 +++++++++++++++++++++ .../services/service/PollenServiceSupport.java | 4 + .../pollen/services/service/PollenUserService.java | 34 ++++ .../pollen/services/service/mail/EmailService.java | 11 +- pollen-ui-riot-js/src/main/web/i18n/fr.json | 13 +- pollen-ui-riot-js/src/main/web/js/UserService.js | 11 ++ .../src/main/web/tag/UserProfile.tag.html | 8 - .../tag/components/UserEmailAddressList.tag.html | 172 +++++++++++++++---- pom.xml | 13 ++ 17 files changed, 450 insertions(+), 46 deletions(-) create mode 100644 pollen-persistence/src/main/resources/db/migration/h2/V3_2_0_2__add_PGP_public_key_in_email.sql create mode 100644 pollen-persistence/src/main/resources/db/migration/postgresql/V3_2_0_2__add_PGP_public_key_in_email.sql create mode 100644 pollen-services/src/main/java/org/chorem/pollen/services/service/CryptoService.java -- To stop receiving notification emails like this one, please contact chorem.org SCM administrator <admin+scm@chorem.org>.
This is an automated email from the git hooks/post-receive script. New commit to branch develop in repository pollen. See https://gitlab.nuiton.org/chorem/pollen.git commit 772268b108a394c10d8288b790776d2ec134fc02 Author: Sylvain Bavencoff <bavencoff@codelutin.com> Date: Wed May 30 10:39:24 2018 +0200 refs #79 : envoie de mail chiffré --- .../h2/V3_2_0_2__add_PGP_public_key_in_email.sql | 2 + .../V3_2_0_2__add_PGP_public_key_in_email.sql | 2 + pollen-persistence/src/main/xmi/pollen.properties | 2 +- pollen-persistence/src/main/xmi/pollen.zargo | Bin 30846 -> 30924 bytes .../rest/api/PollenRestApiRequestFilter.java | 4 +- .../chorem/pollen/rest/api/v1/PollenUserApi.java | 18 ++ pollen-services/pom.xml | 9 + .../services/bean/PollenUserEmailAddressBean.java | 10 ++ .../pollen/services/service/CryptoService.java | 183 +++++++++++++++++++++ .../services/service/PollenServiceSupport.java | 4 + .../pollen/services/service/PollenUserService.java | 34 ++++ .../pollen/services/service/mail/EmailService.java | 11 +- pollen-ui-riot-js/src/main/web/i18n/fr.json | 13 +- pollen-ui-riot-js/src/main/web/js/UserService.js | 11 ++ .../src/main/web/tag/UserProfile.tag.html | 8 - .../tag/components/UserEmailAddressList.tag.html | 172 +++++++++++++++---- pom.xml | 13 ++ 17 files changed, 450 insertions(+), 46 deletions(-) diff --git a/pollen-persistence/src/main/resources/db/migration/h2/V3_2_0_2__add_PGP_public_key_in_email.sql b/pollen-persistence/src/main/resources/db/migration/h2/V3_2_0_2__add_PGP_public_key_in_email.sql new file mode 100644 index 00000000..9d348609 --- /dev/null +++ b/pollen-persistence/src/main/resources/db/migration/h2/V3_2_0_2__add_PGP_public_key_in_email.sql @@ -0,0 +1,2 @@ +-- add invitation sent in VoterListMember +alter table pollenuserEmailaddress add pgppublickey LONGVARCHAR; \ No newline at end of file diff --git a/pollen-persistence/src/main/resources/db/migration/postgresql/V3_2_0_2__add_PGP_public_key_in_email.sql b/pollen-persistence/src/main/resources/db/migration/postgresql/V3_2_0_2__add_PGP_public_key_in_email.sql new file mode 100644 index 00000000..d0ea8eb2 --- /dev/null +++ b/pollen-persistence/src/main/resources/db/migration/postgresql/V3_2_0_2__add_PGP_public_key_in_email.sql @@ -0,0 +1,2 @@ +-- add invitation sent in VoterListMember +alter table pollenuserEmailaddress add pgppublickey TEXT; diff --git a/pollen-persistence/src/main/xmi/pollen.properties b/pollen-persistence/src/main/xmi/pollen.properties index 4c4c9289..8001c99f 100644 --- a/pollen-persistence/src/main/xmi/pollen.properties +++ b/pollen-persistence/src/main/xmi/pollen.properties @@ -18,7 +18,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. # #L% ###m -model.tagvalue.version=3.2.0.1 +model.tagvalue.version=3.2.0.2 #model.tagValue.notGenerateToString=true #model.tagValue.constantPrefix=PROPERTY_ #model.tagValue.useEnumerationName=true diff --git a/pollen-persistence/src/main/xmi/pollen.zargo b/pollen-persistence/src/main/xmi/pollen.zargo index 18196270..52b95159 100644 Binary files a/pollen-persistence/src/main/xmi/pollen.zargo and b/pollen-persistence/src/main/xmi/pollen.zargo differ diff --git a/pollen-rest-api/src/main/java/org/chorem/pollen/rest/api/PollenRestApiRequestFilter.java b/pollen-rest-api/src/main/java/org/chorem/pollen/rest/api/PollenRestApiRequestFilter.java index c7a03a06..ce6e30b6 100644 --- a/pollen-rest-api/src/main/java/org/chorem/pollen/rest/api/PollenRestApiRequestFilter.java +++ b/pollen-rest-api/src/main/java/org/chorem/pollen/rest/api/PollenRestApiRequestFilter.java @@ -36,6 +36,7 @@ import org.chorem.pollen.services.PollenServiceContext; import org.chorem.pollen.services.PollenUIContext; import org.chorem.pollen.services.service.ChoiceService; import org.chorem.pollen.services.service.CommentService; +import org.chorem.pollen.services.service.CryptoService; import org.chorem.pollen.services.service.FavoriteListService; import org.chorem.pollen.services.service.FeedService; import org.chorem.pollen.services.service.FeedbackService; @@ -138,7 +139,8 @@ public class PollenRestApiRequestFilter implements ContainerRequestFilter, Conta SocialAuthService.class, GtuService.class, TransverseService.class, - MailBoxService.class); + MailBoxService.class, + CryptoService.class); /** Logger. */ diff --git a/pollen-rest-api/src/main/java/org/chorem/pollen/rest/api/v1/PollenUserApi.java b/pollen-rest-api/src/main/java/org/chorem/pollen/rest/api/v1/PollenUserApi.java index d3e19227..ef2fa799 100644 --- a/pollen-rest-api/src/main/java/org/chorem/pollen/rest/api/v1/PollenUserApi.java +++ b/pollen-rest-api/src/main/java/org/chorem/pollen/rest/api/v1/PollenUserApi.java @@ -33,6 +33,7 @@ import org.chorem.pollen.services.bean.PaginationResultBean; import org.chorem.pollen.services.bean.PollenEntityId; import org.chorem.pollen.services.bean.PollenEntityRef; import org.chorem.pollen.services.bean.PollenUserBean; +import org.chorem.pollen.services.bean.PollenUserEmailAddressBean; import org.chorem.pollen.services.bean.resource.ResourceFileBean; import org.chorem.pollen.services.bean.resource.ResourceStreamBean; import org.chorem.pollen.services.service.InvalidFormException; @@ -273,4 +274,21 @@ public class PollenUserApi { throws PollenDefaultEmailAddressException { pollenUserService.removeEmailAddress(userId.getEntityId(), emailAddressId.getEntityId()); } + + @Path("/users/{userId}/email/{emailAddressId}") + @POST + public PollenUserEmailAddressBean saveEmailAddress(@Context PollenUserService pollenUserService, + @PathParam("userId") PollenEntityId<PollenUser> userId, + PollenUserEmailAddressBean emailAddress) + throws PollenDefaultEmailAddressException { + return pollenUserService.editEmailAddress(userId.getEntityId(), emailAddress); + } + + @Path("/user/email/{emailAddressId}") + @POST + public PollenUserEmailAddressBean saveEmailAddress(@Context PollenUserService pollenUserService, + PollenUserEmailAddressBean emailAddress) + throws PollenDefaultEmailAddressException { + return pollenUserService.editEmailAddress(emailAddress); + } } diff --git a/pollen-services/pom.xml b/pollen-services/pom.xml index e8d1d3a4..30c9ff2d 100644 --- a/pollen-services/pom.xml +++ b/pollen-services/pom.xml @@ -224,6 +224,15 @@ <artifactId>socialauth</artifactId> </dependency> + <dependency> + <groupId>org.bouncycastle</groupId> + <artifactId>bcprov-jdk15on</artifactId> + </dependency> + <dependency> + <groupId>org.bouncycastle</groupId> + <artifactId>bcpg-jdk15on</artifactId> + </dependency> + </dependencies> <build> diff --git a/pollen-services/src/main/java/org/chorem/pollen/services/bean/PollenUserEmailAddressBean.java b/pollen-services/src/main/java/org/chorem/pollen/services/bean/PollenUserEmailAddressBean.java index 24391a5e..a976447d 100644 --- a/pollen-services/src/main/java/org/chorem/pollen/services/bean/PollenUserEmailAddressBean.java +++ b/pollen-services/src/main/java/org/chorem/pollen/services/bean/PollenUserEmailAddressBean.java @@ -32,6 +32,8 @@ public class PollenUserEmailAddressBean extends PollenBean<PollenUserEmailAddres protected boolean validated; + protected String pgpPublicKey; + public PollenUserEmailAddressBean() { super(PollenUserEmailAddress.class); } @@ -51,4 +53,12 @@ public class PollenUserEmailAddressBean extends PollenBean<PollenUserEmailAddres public void setValidated(boolean validated) { this.validated = validated; } + + public String getPgpPublicKey() { + return pgpPublicKey; + } + + public void setPgpPublicKey(String pgpPublicKey) { + this.pgpPublicKey = pgpPublicKey; + } } diff --git a/pollen-services/src/main/java/org/chorem/pollen/services/service/CryptoService.java b/pollen-services/src/main/java/org/chorem/pollen/services/service/CryptoService.java new file mode 100644 index 00000000..e60e0ec6 --- /dev/null +++ b/pollen-services/src/main/java/org/chorem/pollen/services/service/CryptoService.java @@ -0,0 +1,183 @@ +package org.chorem.pollen.services.service; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.bouncycastle.bcpg.ArmoredOutputStream; +import org.bouncycastle.bcpg.CompressionAlgorithmTags; +import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openpgp.PGPCompressedDataGenerator; +import org.bouncycastle.openpgp.PGPEncryptedDataGenerator; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPLiteralData; +import org.bouncycastle.openpgp.PGPLiteralDataGenerator; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; +import org.bouncycastle.openpgp.PGPUtil; +import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; +import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator; +import org.chorem.pollen.persistence.entity.PollenUserEmailAddress; +import org.chorem.pollen.services.PollenTechnicalException; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.SecureRandom; +import java.security.Security; +import java.util.Date; +import java.util.Iterator; +import java.util.Optional; + +public class CryptoService extends PollenServiceSupport { + + private static final Log log = LogFactory.getLog(CryptoService.class); + + public static final String PROVIDER = "BC"; + + static final SecureRandom SECURE_RANDOM = new SecureRandom(); + + public PGPPublicKey getPublicKey(String email) { + + PGPPublicKey pgpPublicKey = null; + + Optional<String> publicKeyString = getPollenUserEmailAddressDao() + .forEmailAddressEquals(email) + .tryFindUnique() + .toJavaUtil() + .map(PollenUserEmailAddress::getPgpPublicKey); + if (publicKeyString.isPresent()) { + try { + InputStream publicKeyInput = new ByteArrayInputStream(publicKeyString.get().getBytes()); + + pgpPublicKey = readPublicKey(publicKeyInput); + + } catch (IOException | PGPException e) { + throw new PollenTechnicalException("unable to read PGP public key for email : " + email, e); + } + + } + + return pgpPublicKey; + } + + protected PGPPublicKey readPublicKey(InputStream input) throws IOException, PGPException { + PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection( + PGPUtil.getDecoderStream(input), new JcaKeyFingerprintCalculator()); + + // + // we just loop through the collection till we find a key suitable for encryption, in the real + // world you would probably want to be a bit smarter about this. + // + + Iterator keyRingIter = pgpPub.getKeyRings(); + while (keyRingIter.hasNext()) { + PGPPublicKeyRing keyRing = (PGPPublicKeyRing)keyRingIter.next(); + + Iterator keyIter = keyRing.getPublicKeys(); + while (keyIter.hasNext()) + { + PGPPublicKey key = (PGPPublicKey)keyIter.next(); + + if (key.isEncryptionKey()) + { + return key; + } + } + } + + throw new IllegalArgumentException("Can't find encryption key in key ring."); + } + + protected byte[] compressMessage(byte[] message) throws IOException { + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(CompressionAlgorithmTags.ZIP); + OutputStream cos = comData.open(bOut); // open it with the final destination + + PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator(); + + // we want to generate compressed data. This might be a user option later, + // in which case we would pass in bOut. + OutputStream pOut = lData.open(cos, // the compressed output stream + PGPLiteralData.BINARY, + PGPLiteralDataGenerator.CONSOLE, // "filename" to store + message.length, // length of clear data + new Date() // current time + ); + + pOut.write(message); + pOut.close(); + + comData.close(); + + return bOut.toByteArray(); + } + + protected byte[] encryptMessage(byte[] message, PGPPublicKey pgpPublicKey) throws IOException, PGPException { + + if (Security.getProvider(PROVIDER) == null) { + Security.addProvider(new BouncyCastleProvider()); + } + + byte[] compressMessage = compressMessage(message); + + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + + OutputStream out = new ArmoredOutputStream(bout); + + PGPEncryptedDataGenerator encryptedDataGenerator = createEncryptedDataGenerator(pgpPublicKey); + + OutputStream encOut = encryptedDataGenerator.open(out, compressMessage.length); + + encOut.write(compressMessage); + encOut.close(); + out.close(); + + return bout.toByteArray(); + + } + + private PGPEncryptedDataGenerator createEncryptedDataGenerator(PGPPublicKey pgpPublicKey) { + JcePGPDataEncryptorBuilder encryptorBuilder = new JcePGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.CAST5) + .setSecureRandom(SECURE_RANDOM) + .setWithIntegrityPacket(true) + .setProvider(PROVIDER); + + PGPEncryptedDataGenerator encryptedDataGenerator = new PGPEncryptedDataGenerator(encryptorBuilder); + + JcePublicKeyKeyEncryptionMethodGenerator method = new JcePublicKeyKeyEncryptionMethodGenerator(pgpPublicKey) + .setProvider(PROVIDER) + .setSecureRandom(SECURE_RANDOM); + + encryptedDataGenerator.addMethod(method); + + return encryptedDataGenerator; + } + + public String encryptMailIfAsKey(String body, String email) { + + PGPPublicKey publicKey = getPublicKey(email); + + if (publicKey != null) { + + try { + byte[] encryptMessage = encryptMessage(body.getBytes(), publicKey); + return new String(encryptMessage); + } catch (IOException | PGPException e) { + if (log.isErrorEnabled()) { + log.error("error on encrypte mail to email", e); + } + return body; + } + + } + + return body; + + } + +} diff --git a/pollen-services/src/main/java/org/chorem/pollen/services/service/PollenServiceSupport.java b/pollen-services/src/main/java/org/chorem/pollen/services/service/PollenServiceSupport.java index d46beb01..6008b407 100644 --- a/pollen-services/src/main/java/org/chorem/pollen/services/service/PollenServiceSupport.java +++ b/pollen-services/src/main/java/org/chorem/pollen/services/service/PollenServiceSupport.java @@ -188,6 +188,10 @@ public abstract class PollenServiceSupport implements PollenService { return newService(VoteService.class); } + protected CryptoService getCryptoService() { + return newService(CryptoService.class); + } + protected <E extends PollenService> E newService(Class<E> serviceClass) { return serviceContext.newService(serviceClass); } diff --git a/pollen-services/src/main/java/org/chorem/pollen/services/service/PollenUserService.java b/pollen-services/src/main/java/org/chorem/pollen/services/service/PollenUserService.java index 39318bb4..e6d4dabb 100644 --- a/pollen-services/src/main/java/org/chorem/pollen/services/service/PollenUserService.java +++ b/pollen-services/src/main/java/org/chorem/pollen/services/service/PollenUserService.java @@ -109,6 +109,7 @@ public class PollenUserService extends PollenServiceSupport implements PollenSer bean.setEntityId(entity.getTopiaId()); bean.setEmailAddress(entity.getEmailAddress()); bean.setValidated(entity.getActivationToken() == null); + bean.setPgpPublicKey(entity.getPgpPublicKey()); return bean; } @@ -579,4 +580,37 @@ public class PollenUserService extends PollenServiceSupport implements PollenSer } return result; } + + public PollenUserEmailAddressBean editEmailAddress(PollenUserEmailAddressBean emailAddress) throws PollenDefaultEmailAddressException { + PollenUser connectedUser = checkAndGetConnectedUser(); + return editEmailAddressFromUser(connectedUser, emailAddress); + } + + public PollenUserEmailAddressBean editEmailAddress(String userId, PollenUserEmailAddressBean emailAddress) throws PollenDefaultEmailAddressException { + checkConnectedUserOrAdmin(userId); + return editEmailAddressFromUser(getUser0(userId), emailAddress); + } + + + protected PollenUserEmailAddressBean editEmailAddressFromUser(PollenUser user, PollenUserEmailAddressBean emailAddress) { + checkIsConnectedRequired(); + + checkNotNull(emailAddress); + checkIsPersisted(emailAddress); + + PollenUserEmailAddress result = getPollenUserEmailAddressDao().forTopiaIdEquals(emailAddress.getEntityId()).findUnique(); + + if (!user.containsEmailAddresses(result)) { + + throw new InvalidEntityLinkException(PollenUser.PROPERTY_EMAIL_ADDRESSES, user, result); + + } + + result.setPgpPublicKey(emailAddress.getPgpPublicKey()); + + commit(); + + return toPollenUserEmailAddressBean(result); + + } } diff --git a/pollen-services/src/main/java/org/chorem/pollen/services/service/mail/EmailService.java b/pollen-services/src/main/java/org/chorem/pollen/services/service/mail/EmailService.java index 1b9f71d0..541cbb4a 100644 --- a/pollen-services/src/main/java/org/chorem/pollen/services/service/mail/EmailService.java +++ b/pollen-services/src/main/java/org/chorem/pollen/services/service/mail/EmailService.java @@ -28,6 +28,7 @@ import com.github.mustachejava.MustacheFactory; import com.google.common.base.Charsets; import com.google.common.base.Preconditions; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.IterableUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; @@ -508,7 +509,15 @@ public class EmailService extends PollenServiceSupport { mustache.execute(stringWriter, mail); - return stringWriter.toString() + mail.getSigning(); + String body = stringWriter.toString() + mail.getSigning(); + + if (mail.getTos().size() == 1) { + + body = getCryptoService().encryptMailIfAsKey(body, IterableUtils.get(mail.getTos(), 0)); + + } + + return body; } diff --git a/pollen-ui-riot-js/src/main/web/i18n/fr.json b/pollen-ui-riot-js/src/main/web/i18n/fr.json index 958bdef1..d20ebba6 100644 --- a/pollen-ui-riot-js/src/main/web/i18n/fr.json +++ b/pollen-ui-riot-js/src/main/web/i18n/fr.json @@ -649,5 +649,16 @@ "emailAddressList_deleteEmailAddress": "Supprimer l'adresse électronique", "emailAddressList_deleteEmailAddressMessage": "Supprimer l'adresse électronique {0} ?", "emailAddressList_newAddressPlaceholder": "Entrez une nouvelle adresse électronique", - "emailAddressList_addEmailAddress": "Ajouter une adresse électronique" + "emailAddressList_addEmailAddress": "Ajouter une adresse électronique", + "emailAddressList_addPgpPublicKey": "Ajouter une clé de chiffrement", + "emailAddressList_editPgpPublicKey": "Modifier la clé de chiffrement", + "emailAddressList_pgpPublicKey": "Clé public PGP", + "emailAddressList_pgpPublicKeyPlaceHolder": "-----BEGIN PGP PUBLIC KEY BLOCK-----", + "emailAddressList_cancelPgpPublicKey": "Annuler", + "emailAddressList_removePgpPublicKey": "Supprimer la clé", + "emailAddressList_removePgpPublicKey_ask": "Supprimer la clé public PGP ?", + "emailAddressList_savePgpPublicKey": "Enregistrer la clé", + "emailAddressList_sendPgpPublicKey": "Envoyer la clé", + "emailAddressList_updatedPgpPublicKey": "La clé public PGP est enregistrée avec succès.", + "emailAddressList_removedPgpPublicKey": "La clé public PGP est supprimée avec succès." } diff --git a/pollen-ui-riot-js/src/main/web/js/UserService.js b/pollen-ui-riot-js/src/main/web/js/UserService.js index 10b9f78e..44578082 100644 --- a/pollen-ui-riot-js/src/main/web/js/UserService.js +++ b/pollen-ui-riot-js/src/main/web/js/UserService.js @@ -139,6 +139,17 @@ class UserService extends FetchService { } return this.doDelete(url + "/avatar"); } + + saveEmailAdress(emailAddress, userId) { + let url; + if (userId) { + url = this._getUsersUrlPrefix(userId); + } else { + url = this._getUserUrlPrefix(); + } + url += "/email/" + emailAddress.id; + return this.post(url, emailAddress); + } } export default singleton(UserService); diff --git a/pollen-ui-riot-js/src/main/web/tag/UserProfile.tag.html b/pollen-ui-riot-js/src/main/web/tag/UserProfile.tag.html index f3be0161..e30bf2fe 100644 --- a/pollen-ui-riot-js/src/main/web/tag/UserProfile.tag.html +++ b/pollen-ui-riot-js/src/main/web/tag/UserProfile.tag.html @@ -399,14 +399,6 @@ import "./components/Avatar.tag.html"; margin-top: 20px; } - .email-address { - display: flex; - justify-content: space-around; - align-items: center; - margin: 1px 0; - padding: 1px 5px; - } - .email-address.odd { background-color: var(--list-alternate-row); } diff --git a/pollen-ui-riot-js/src/main/web/tag/components/UserEmailAddressList.tag.html b/pollen-ui-riot-js/src/main/web/tag/components/UserEmailAddressList.tag.html index 03a9adc1..8d14a457 100644 --- a/pollen-ui-riot-js/src/main/web/tag/components/UserEmailAddressList.tag.html +++ b/pollen-ui-riot-js/src/main/web/tag/components/UserEmailAddressList.tag.html @@ -20,37 +20,84 @@ --> <UserEmailAddressList> <h3 class="c-heading"><i class="fa fa-at"/> {_t.emailAddresses}</h3> - <div each="{emailAddress, index in opts.user.emailAddresses}" class="email-address {index % 2 == 0 ? 'even' : 'odd'}"> - <span class="email-address-label {validation-pending : !emailAddress.validated} {default-address : parent.opts.user.defaultEmailAddress.id === emailAddress.id}"> - {emailAddress.emailAddress} - </span> - <button type="button" - class="c-button u-small" - if={!emailAddress.validated} - title="{parent._t.resendValidation}" - onclick={parent.resendValidation(emailAddress.emailAddress)}> - <i class="fa fa-paper-plane" aria-hidden="true"></i> - </button> - <button type="button" - class="c-button u-small c-button--success" - if={!emailAddress.validated && parent.opts.admin} - title="{parent._t.validate}" - onclick={validate(emailAddress.id, index)}> - <i class="fa fa-check" aria-hidden="true"></i> - </button> - <button if="{parent.opts.user.defaultEmailAddress.id !== emailAddress.id && emailAddress.validated}" - type="button" class="c-button u-small c-button--info" - title="{parent._t.defaultEmailAddress}" - onclick="{parent.setDefaultEmailAddress(emailAddress.id, index)}"> - <i class="fa fa-envelope"></i> - </button> - <button disabled="{parent.opts.user.defaultEmailAddress.id === emailAddress.id}" - type="button" class="c-button u-small c-button--error" - title="{parent._t.deleteEmailAddress}" - onclick="{parent.deleteEmailAddress(emailAddress.id, index)}"> - <i class="fa fa-trash"></i> - </button> - </div> + <ul> + <li each="{emailAddress, index in opts.user.emailAddresses}" class="email-address {index % 2 == 0 ? 'even' : 'odd'}"> + <div class="email-address-header"> + <span class="email-address-label {validation-pending : !emailAddress.validated} {default-address : parent.opts.user.defaultEmailAddress.id === emailAddress.id}"> + {emailAddress.emailAddress} + </span> + <button type="button" + class="c-button u-small" + if={!emailAddress.validated} + title="{parent._t.resendValidation}" + onclick={parent.resendValidation(emailAddress.emailAddress)}> + <i class="fa fa-paper-plane" aria-hidden="true"></i> + </button> + <button type="button" + class="c-button u-small c-button--success" + if={!emailAddress.validated && parent.opts.admin} + title="{parent._t.validate}" + onclick={validate(emailAddress.id, index)}> + <i class="fa fa-check" aria-hidden="true"></i> + </button> + <button if="{parent.opts.user.defaultEmailAddress.id !== emailAddress.id && emailAddress.validated}" + type="button" class="c-button u-small c-button--info" + title="{parent._t.defaultEmailAddress}" + onclick="{parent.setDefaultEmailAddress(emailAddress.id, index)}"> + <i class="fa fa-envelope"></i> + </button> + <button type="button" + class="c-button u-small c-button--warning" + if={emailAddress.validated} + title="{emailAddress.pgpPublicKey ? parent._t.editPgpPublicKey : parent._t.addPgpPublicKey}" + onclick={parent.openPgpPublicKey(emailAddress)}> + <i class="fa {fa-lock: emailAddress.pgpPublicKey, fa-unlock: !emailAddress.pgpPublicKey}" aria-hidden="true"></i> + </button> + <button disabled="{parent.opts.user.defaultEmailAddress.id === emailAddress.id}" + type="button" class="c-button u-small c-button--error" + title="{parent._t.deleteEmailAddress}" + onclick="{parent.deleteEmailAddress(emailAddress.id, index)}"> + <i class="fa fa-trash"></i> + </button> + </div> + <form class="identity-form" show={emailAddress.showKey}> + <HumanInput onsubmit={savePgpPublicKey(emailAddress)}/> + <div class="o-form-element"> + <label class="c-label" for="pgpPublicKey">{parent._t.pgpPublicKey}</label> + <textarea ref="pgpPublicKey-{emailAddress.id}" + class="c-field c-field--label" + name="pgpPublicKey" + required + placeholder="{parent._t.pgpPublicKeyPlaceHolder}">{emailAddress.pgpPublicKey}</textarea> + </div> + <div class="actions"> + <div class="actions-left"> + <button type="button" + onclick={parent.cancelPgpPublicKey(emailAddress)} + class="c-button c-button--ghost-info"> + <i class="fa fa-undo" aria-hidden="true"></i> + {parent._t.cancelPgpPublicKey} + </button> + </div> + + <div class="actions-right"> + <button type="button" + if={emailAddress.pgpPublicKey} + onclick={parent.removePgpPublicKey(emailAddress)} + class="c-button c-button--error"> + <i class="fa fa-times" aria-hidden="true"></i> + {parent._t.removePgpPublicKey} + </button> + <button type="submit" + class="c-button c-button--info"> + <i class="fa fa-check" aria-hidden="true"></i> + {emailAddress.pgpPublicKey ? parent._t.savePgpPublicKey : parent._t.sendPgpPublicKey} + </button> + </div> + </div> + </div> + </li> + </ul> <div ref="new-email-form" class="new-email-form"> <div class="o-form-element c-input-group"> <div class="o-field o-field--icon-right"> @@ -77,7 +124,7 @@ import Message from "../../js/Message"; import userService from "../../js/UserService"; import authService from "../../js/AuthService"; - + this.installBundle(session, "emailAddressList"); this.errors = {}; @@ -114,6 +161,56 @@ }); }; + this.setDefaultEmailAddress = (emailAddressId, index) => () => { + userService.setDefaultEmailAddress(emailAddressId, this.opts.admin ? this.opts.user.id : null).then(() => { + this.opts.user.defaultEmailAddress = this.opts.user.emailAddresses[index]; + this.update(); + }); + }; + + this.openPgpPublicKey = emailAddress => () => { + this.opts.user.emailAddresses.forEach(mail => { + mail.showKey = emailAddress === mail; + }); + }; + + this.cancelPgpPublicKey = emailAddress => () => { + this.refs["pgpPublicKey-" + emailAddress.id].value = emailAddress.pgpPublicKey; + emailAddress.showKey = false; + }; + + this.removePgpPublicKey = emailAddress => () => { + this.confirm(this._t.removePgpPublicKey_ask).then((confirm) => { + if (confirm) { + this.savePgpPublicKey2(emailAddress, null).then(() => { + this.bus.trigger("message", this._t.removedPgpPublicKey, "success"); + this.update(); + }); + } + }); + }; + + this.savePgpPublicKey = emailAddress => () => { + this.savePgpPublicKey2(emailAddress, this.refs["pgpPublicKey-" + emailAddress.id].value).then(() => { + this.bus.trigger("message", this._t.updatedPgpPublicKey, "success"); + this.update(); + }); + }; + + this.savePgpPublicKey2 = (emailAddress, pgpPublicKey) => { + let emailAddress2 = { + id: emailAddress.id, + emailAddress: emailAddress.emailAddress, + validated: emailAddress.validated, + pgpPublicKey: pgpPublicKey + }; + return userService.saveEmailAdress(emailAddress2, this.opts.admin ? this.opts.user.id : null).then((email) => { + emailAddress.showKey = false; + emailAddress.pgpPublicKey = email.pgpPublicKey; + return emailAddress; + }); + }; + this.submitEmailAddress = () => { let emailAddress = this.refs.newEmailAddress.value; userService.addEmailAddressToUser(emailAddress, this.opts.admin ? this.opts.user.id : null).then((result) => { @@ -131,11 +228,14 @@ <style> .email-address { + margin: 1px 0; + padding: 1px 5px; + } + + .email-address-header { display: flex; justify-content: space-around; align-items: center; - margin: 1px 0; - padding: 1px 5px; } .email-address.odd { @@ -147,6 +247,10 @@ padding: 0 5px; } + .email-address .actions { + justify-content: space-between; + } + .email-address-label.default-address { font-weight: bold; } diff --git a/pom.xml b/pom.xml index 47b6aedb..f2cec315 100644 --- a/pom.xml +++ b/pom.xml @@ -195,6 +195,7 @@ <tomcatEmbedLoggingVersion>8.5.2</tomcatEmbedLoggingVersion> <hibernateVersion>5.2.10.Final</hibernateVersion> <httpCommonsHttpclientVersion>4.5.5</httpCommonsHttpclientVersion> + <bouncycastleVersion>1.59</bouncycastleVersion> <pollenI18nBundle>pollen-i18n</pollenI18nBundle> <!-- license to use --> @@ -478,6 +479,18 @@ <scope>runtime</scope> </dependency> + <!--Crypto--> + <dependency> + <groupId>org.bouncycastle</groupId> + <artifactId>bcprov-jdk15on</artifactId> + <version>${bouncycastleVersion}</version> + </dependency> + <dependency> + <groupId>org.bouncycastle</groupId> + <artifactId>bcpg-jdk15on</artifactId> + <version>${bouncycastleVersion}</version> + </dependency> + <!-- Others --> <dependency> -- To stop receiving notification emails like this one, please contact chorem.org SCM administrator <admin+scm@chorem.org>.
participants (1)
-
chorem.org scm