diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2c2908b..da00291 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,8 +13,9 @@ jobs: steps: - uses: actions/checkout@v2 - name: Set up Java - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: + 'distribution': adopt java-version: '11' - name: Build with Maven run: mvn -B package --file pom.xml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d07aa91..f4c1668 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,8 +14,9 @@ jobs: uses: actions/checkout@v2 - name: Set up Java - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: + 'distribution': adopt java-version: '11' - name: Build with Maven 🔧 diff --git a/README.MD b/README.MD index 5f68943..38dc82f 100644 --- a/README.MD +++ b/README.MD @@ -12,6 +12,15 @@ Allows confluence users to write contracts in a confluence macro which can be si - easily send email to signers of the contract - receive notifications, when your contract was signed +## ClassCastException issue +![ClassCastException in Macro](./docs/img/classcastexception.png) + +If you observe issues in the Macro resulting in a `ClassCastException` please update digital-signature to version 7.0.5, +clear the plugin cache (one last time) and restart confluence. + +For background information please refer to [#82](https://github.com/baloise/digital-signature/issues/82) +and ['How to clear Confluence plugins cache'](https://confluence.atlassian.com/confkb/how-to-clear-confluence-plugins-cache-297664846.html). + ## Privacy Policy - We do not transfer or store any data outside your Atlassian product. - We have no access to any data you stored within your Atlassian product. @@ -25,8 +34,6 @@ the [Wiki...](https://github.com/baloise/digital-signature/wiki/Signature-Macro- ## Using Confluence Data Center Version Digital-signature can be used on Confluence Data Center, however it is not yet officially tested and approved. -When performing an update you might have to invalidate the plugin cache and restart Confluence. For details refer -to [#68](https://github.com/baloise/digital-signature/issues/68). ## Feature overview ### Insert / edit macro @@ -45,7 +52,7 @@ to [#68](https://github.com/baloise/digital-signature/issues/68). ### Mail notification ![](./docs/img/report_email_export.png) -![](./docs/img/send_mail.png.png) +![](./docs/img/send_mail.png) ## Contribute Keep it simple: every contribution is welcome. Either if you report an issue, help on solving one, or contribute to the diff --git a/development.md b/development.md deleted file mode 100644 index 3820c4b..0000000 --- a/development.md +++ /dev/null @@ -1,42 +0,0 @@ -# Install -https://www.atlassian.com/software/confluence/download-archives - -- Apache Commons FileUpload Bundle -http://central.maven.org/maven2/commons-fileupload/commons-fileupload/1.3/commons-fileupload-1.3.jar -- Atlassian PDK Install Plugin -http://maven-us.nuxeo.org/nexus/content/repositories/public/com/atlassian/pdkinstall/pdkinstall-plugin/0.6/pdkinstall-plugin-0.6.jar - -## License -https://my.atlassian.com/products/index - -# Run -```shell script -set CATALINA_HOME=C:\Users\Public\dev\atlas\Confluence -set JPDA_ADDRESS=4444 -set JPDA_TRANSPORT=dt_socket -%CATALINA_HOME%\bin\catalina.bat jpda start -``` - -http://127.0.0.1:8090/ -```shell script -atlas-install-plugin -p 8090 --context-path / --plugin-key com.baloise.confluence.digital-signature -``` - -or uncomment the atlassian-pdk configuration in pom.xml and use -mvn package confluence:install - -------------------- - -Pure Maven setup (not for the faint of heart, startup is slow on my box) -you will be able to remote debug on port 5005 - -```shell script -mvn confluence:debug -Dproduct.version=7.4.0 -``` - -To redeploy use -```shell script -mvn package confluence:install -Dproduct.version=7.4.0 -``` - -As smtp server use `https://mailtrap.io/` diff --git a/docs/docker.md b/docs/docker.md new file mode 100644 index 0000000..ac56309 --- /dev/null +++ b/docs/docker.md @@ -0,0 +1,30 @@ +Setup following a [tutorial from coffeetime.solutions]( http://coffeetime.solutions/run-atlassian-jira-and-confluence-with-postgresql-on-docker/#Overview_of_series_How_to_run_Jira_and_Confluence_behind_NGINX_reverse_proxy_on_Docker): + +```bash +mkdir -p $HOME/docker/volumes/postgres +mkdir -p $HOME/docker/volumes/confluence +docker run --name postgres \ + -v $HOME/docker/volumes/postgres:/var/lib/postgresql/data \ + -e POSTGRES_PASSWORD=mysecretpassword \ + -d postgres +docker run --name=confluence \ + -v $HOME/docker/volumes/confluence:/var/atlassian/application-data/confluence \ + -d \ + -p 8090:8090 \ + -p 8091:8091 \ + -p 5005:5005 \ + -e JVM_SUPPORT_RECOMMENDED_ARGS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005" \ + atlassian/confluence-server:latest +docker inspect postgres | grep IPAddress # get the IP address of the postgres container +``` + +Start confluence setup and configure Postgres: +- jdbc:postgresql://192.168.65.2:5432/postgres (`docker inspect postgres | grep IP` to get ip address) + - in case of IP change search and replace in `/var/atlassian/application-data/confluence.cfg.xml` +- user: postgres +- password: mysecretpassword (defined above) + +![](img/db.png) + +- Skip tutorial +- Create new space "Test" diff --git a/docs/img/classcastexception.png b/docs/img/classcastexception.png new file mode 100644 index 0000000..d23dad2 Binary files /dev/null and b/docs/img/classcastexception.png differ diff --git a/docs/img/db.png b/docs/img/db.png new file mode 100644 index 0000000..c0fa49b Binary files /dev/null and b/docs/img/db.png differ diff --git a/lombok.config b/lombok.config new file mode 100644 index 0000000..df71bb6 --- /dev/null +++ b/lombok.config @@ -0,0 +1,2 @@ +config.stopBubbling = true +lombok.addLombokGeneratedAnnotation = true diff --git a/pom.xml b/pom.xml index 179ea6e..c84e0f0 100644 --- a/pom.xml +++ b/pom.xml @@ -4,23 +4,24 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + digital-signature + This is the com.baloise.confluence:digital-signature plugin for Atlassian Confluence. + atlassian-plugin 4.0.0 com.baloise.confluence digital-signature 7.0.5 + Baloise http://www.baloise.ch/ - digital-signature - This is the com.baloise.confluence:digital-signature plugin for Atlassian Confluence. - atlassian-plugin - Github https://github.com/baloise/digital-signature/issues + scm:git:https://github.com/baloise/digital-signature.git scm:git:https://github.com/baloise/digital-signature.git @@ -31,30 +32,21 @@ com.vladsch.flexmark flexmark - 0.60.2 + 0.62.2 - junit - junit - 4.13.1 - test + com.google.code.gson + gson + 2.8.9 - com.atlassian.confluence - confluence - ${confluence.version} - provided - - - - com.atlassian.mywork - mywork-api - 1.0.2 + org.projectlombok + lombok + 1.18.22 provided - com.atlassian.plugin atlassian-spring-scanner-annotation @@ -69,42 +61,54 @@ runtime + + com.atlassian.confluence + confluence + ${confluence.version} + provided + + + com.atlassian.mywork + mywork-api + 1.0.2 + provided + javax.inject javax.inject 1 provided - - org.slf4j - slf4j-simple - 2.0.0-alpha1 - test + javax.ws.rs + jsr311-api + 1.1.1 + provided + + org.junit.jupiter + junit-jupiter-engine + 5.8.2 + test + com.atlassian.plugins atlassian-plugins-osgi-testrunner ${plugin.testrunner.version} test - - javax.ws.rs - jsr311-api - 1.1.1 - provided - - - com.google.code.gson - gson - 2.2.2-atlassian-1 - org.mockito mockito-all - 1.9.0 + 1.10.19 + test + + + org.slf4j + slf4j-simple + 2.0.0-alpha1 test diff --git a/src/main/java/com/baloise/confluence/digitalsignature/ContextHelper.java b/src/main/java/com/baloise/confluence/digitalsignature/ContextHelper.java index e610b22..2c67bc6 100644 --- a/src/main/java/com/baloise/confluence/digitalsignature/ContextHelper.java +++ b/src/main/java/com/baloise/confluence/digitalsignature/ContextHelper.java @@ -11,59 +11,59 @@ import static java.lang.String.format; public class ContextHelper { - public Object getOrderedSignatures(Signature signature) { - SortedSet> ret = new TreeSet<>(Comparator.comparing((Function, Date>) Entry::getValue) - .thenComparing(Entry::getKey)); - ret.addAll(signature.getSignatures().entrySet()); - return ret; - } + public Object getOrderedSignatures(Signature2 signature) { + SortedSet> ret = new TreeSet<>(Comparator.comparing((Function, Date>) Entry::getValue) + .thenComparing(Entry::getKey)); + ret.addAll(signature.getSignatures().entrySet()); + return ret; + } - @SafeVarargs - public final Map union(Map... maps) { - Map union = new HashMap<>(); - for (Map map : maps) { - union.putAll(map); - } - return union; + @SafeVarargs + public final Map union(Map... maps) { + Map union = new HashMap<>(); + for (Map map : maps) { + union.putAll(map); } + return union; + } - @SafeVarargs - public final Set union(Set... sets) { - Set union = new HashSet<>(); - for (Set set : sets) { - union.addAll(set); - } - return union; + @SafeVarargs + public final Set union(Set... sets) { + Set union = new HashSet<>(); + for (Set set : sets) { + union.addAll(set); } + return union; + } - public Map getProfiles(UserManager userManager, Set userNames) { - Map ret = new HashMap<>(); - if (Signature.isPetitionMode(userNames)) return ret; - for (String userName : userNames) { - ret.put(userName, getProfileNotNull(userManager, userName)); - } - return ret; + public Map getProfiles(UserManager userManager, Set userNames) { + Map ret = new HashMap<>(); + if (Signature2.isPetitionMode(userNames)) return ret; + for (String userName : userNames) { + ret.put(userName, getProfileNotNull(userManager, userName)); } + return ret; + } - public UserProfile getProfileNotNull(UserManager userManager, String userName) { - UserProfile profile = userManager.getUserProfile(userName); - return profile == null ? new DummyProfile(userName) : profile; - } + public UserProfile getProfileNotNull(UserManager userManager, String userName) { + UserProfile profile = userManager.getUserProfile(userName); + return profile == null ? new DummyProfile(userName) : profile; + } - public SortedSet getOrderedProfiles(UserManager userManager, Set userNames) { - SortedSet ret = new TreeSet<>(new UserProfileByName()); - if (Signature.isPetitionMode(userNames)) return ret; - for (String userName : userNames) { - ret.add(getProfileNotNull(userManager, userName)); - } - return ret; + public SortedSet getOrderedProfiles(UserManager userManager, Set userNames) { + SortedSet ret = new TreeSet<>(new UserProfileByName()); + if (Signature2.isPetitionMode(userNames)) return ret; + for (String userName : userNames) { + ret.add(getProfileNotNull(userManager, userName)); } + return ret; + } - public String mailTo(UserProfile profile) { - return format("%s<%s>", profile.getFullName().trim(), profile.getEmail().trim()); - } + public String mailTo(UserProfile profile) { + return format("%s<%s>", profile.getFullName().trim(), profile.getEmail().trim()); + } - public boolean hasEmail(UserProfile profile) { - return profile != null && profile.getEmail() != null && !profile.getEmail().trim().isEmpty(); - } + public boolean hasEmail(UserProfile profile) { + return profile != null && profile.getEmail() != null && !profile.getEmail().trim().isEmpty(); + } } diff --git a/src/main/java/com/baloise/confluence/digitalsignature/DigitalSignatureMacro.java b/src/main/java/com/baloise/confluence/digitalsignature/DigitalSignatureMacro.java index 00de8cc..f77e527 100644 --- a/src/main/java/com/baloise/confluence/digitalsignature/DigitalSignatureMacro.java +++ b/src/main/java/com/baloise/confluence/digitalsignature/DigitalSignatureMacro.java @@ -1,14 +1,5 @@ package com.baloise.confluence.digitalsignature; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.security.InvalidParameterException; -import java.util.*; - -import org.apache.velocity.tools.generic.DateTool; -import org.jetbrains.annotations.NotNull; -import org.springframework.beans.factory.annotation.Autowired; - import com.atlassian.bandana.BandanaManager; import com.atlassian.confluence.content.render.xhtml.ConversionContext; import com.atlassian.confluence.core.ContentEntityObject; @@ -32,348 +23,334 @@ import com.atlassian.user.Group; import com.atlassian.user.GroupManager; import com.atlassian.user.search.page.Pager; +import org.apache.velocity.tools.generic.DateTool; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.security.InvalidParameterException; +import java.util.*; import static com.atlassian.confluence.renderer.radeox.macros.MacroUtils.defaultVelocityContext; import static com.atlassian.confluence.security.ContentPermission.*; -import static com.atlassian.confluence.setup.bandana.ConfluenceBandanaContext.GLOBAL_CONTEXT; import static com.atlassian.confluence.util.velocity.VelocityUtils.getRenderedTemplate; import static java.util.Arrays.asList; import static java.util.stream.Collectors.toList; @Scanned public class DigitalSignatureMacro implements Macro { - private final int MAX_MAILTO_CHARACTER_COUNT = 500; - private final String REST_PATH = "/rest/signature/1.0"; - private final String DISPLAY_PATH = "/display"; - private final transient Markdown markdown = new Markdown(); - private final PermissionManager permissionManager; - private final Set all = new HashSet<>(); - private BandanaManager bandanaManager; - private UserManager userManager; - private BootstrapManager bootstrapManager; - private PageManager pageManager; - private ContextHelper contextHelper = new ContextHelper(); - private GroupManager groupManager; - private I18nResolver i18nResolver; - - @Autowired - public DigitalSignatureMacro( - @ComponentImport BandanaManager bandanaManager, - @ComponentImport UserManager userManager, - @ComponentImport BootstrapManager bootstrapManager, - @ComponentImport PageManager pageManager, - @ComponentImport PermissionManager permissionManager, - @ComponentImport GroupManager groupManager, - @ComponentImport I18nResolver i18nResolver - ) { - this.bandanaManager = bandanaManager; - this.userManager = userManager; - this.bootstrapManager = bootstrapManager; - this.pageManager = pageManager; - this.permissionManager = permissionManager; - this.groupManager = groupManager; - this.i18nResolver = i18nResolver; - all.add("*"); + private static final int MAX_MAILTO_CHARACTER_COUNT = 500; + private static final String REST_PATH = "/rest/signature/1.0"; + private static final String DISPLAY_PATH = "/display"; + + private final BandanaManager bandanaManager; + private final UserManager userManager; + private final BootstrapManager bootstrapManager; + private final PageManager pageManager; + private final PermissionManager permissionManager; + private final GroupManager groupManager; + private final I18nResolver i18nResolver; + private final transient Markdown markdown = new Markdown(); + private final Set all = new HashSet<>(); + private final ContextHelper contextHelper = new ContextHelper(); + + @Autowired + public DigitalSignatureMacro(@ComponentImport BandanaManager bandanaManager, + @ComponentImport UserManager userManager, + @ComponentImport BootstrapManager bootstrapManager, + @ComponentImport PageManager pageManager, + @ComponentImport PermissionManager permissionManager, + @ComponentImport GroupManager groupManager, + @ComponentImport I18nResolver i18nResolver) { + this.bandanaManager = bandanaManager; + this.userManager = userManager; + this.bootstrapManager = bootstrapManager; + this.pageManager = pageManager; + this.permissionManager = permissionManager; + this.groupManager = groupManager; + this.i18nResolver = i18nResolver; + + all.add("*"); + } + + @Override + public String execute(Map params, String body, ConversionContext conversionContext) { + if (body == null || body.length() <= 10) { + return warning(i18nResolver.getText("com.baloise.confluence.digital-signature.signature.macro.warning.bodyToShort")); } - @Override - public String execute(Map params, String body, ConversionContext conversionContext) { - if (body == null || body.length() <= 10) { - return warning(i18nResolver.getText("com.baloise.confluence.digital-signature.signature.macro.warning.bodyToShort")); - } - - Set userGroups = getSet(params, "signerGroups"); - boolean petitionMode = Signature.isPetitionMode(userGroups); - @SuppressWarnings("unchecked") - Set signers = petitionMode ? all : contextHelper.union( - getSet(params, "signers"), - loadUserGroups(userGroups), - loadInheritedSigners(InheritSigners.ofValue(params.get("inheritSigners")), conversionContext) - ); - ContentEntityObject entity = conversionContext.getEntity(); - Signature signature = sync(new Signature( - entity.getLatestVersionId(), - body, - params.get("title")) - .withNotified(getSet(params, "notified")) - .withMaxSignatures(getLong(params, "maxSignatures", -1)) - .withVisibilityLimit(getLong(params, "visibilityLimit", -1)), - signers - ); - - boolean protectedContent = getBoolean(params, "protectedContent", false); - if (protectedContent && isPage(conversionContext)) { - try { - ensureProtectedPage(conversionContext, (Page) entity, signature); - } catch (Exception e) { - return warning(i18nResolver.getText("com.baloise.confluence.digital-signature.signature.macro.warning.editPermissionRequiredForProtectedContent", "")); - } - } - - return getRenderedTemplate("templates/macro.vm", buildContext(params, conversionContext, entity, signature, protectedContent)); + Set userGroups = getSet(params, "signerGroups"); + boolean petitionMode = Signature2.isPetitionMode(userGroups); + Set signers = petitionMode ? all : contextHelper.union(getSet(params, "signers"), loadUserGroups(userGroups), loadInheritedSigners(InheritSigners.ofValue(params.get("inheritSigners")), conversionContext)); + ContentEntityObject entity = conversionContext.getEntity(); + Signature2 signature = sync(new Signature2(entity.getLatestVersionId(), body, params.get("title")).withNotified(getSet(params, "notified")).withMaxSignatures(getLong(params, "maxSignatures", -1)).withVisibilityLimit(getLong(params, "visibilityLimit", -1)), signers); + + boolean protectedContent = getBoolean(params, "protectedContent", false); + if (protectedContent && isPage(conversionContext)) { + try { + ensureProtectedPage(conversionContext, (Page) entity, signature); + } catch (Exception e) { + return warning(i18nResolver.getText("com.baloise.confluence.digital-signature.signature.macro.warning.editPermissionRequiredForProtectedContent", "")); + } } - @NotNull - private Map buildContext(Map params, ConversionContext conversionContext, ContentEntityObject page, Signature signature, boolean protectedContent) { - ConfluenceUser currentUser = AuthenticatedUserThreadLocal.get(); - String currentUserName = currentUser.getName(); - boolean protectedContentAccess = protectedContent && (permissionManager.hasPermission(currentUser, Permission.EDIT, page) || signature.hasSigned(currentUserName)); + return getRenderedTemplate("templates/macro.vm", buildContext(params, conversionContext, entity, signature, protectedContent)); + } - Map context = defaultVelocityContext(); - context.put("date", new DateTool()); - context.put("markdown", markdown); + @NotNull + private Map buildContext(Map params, ConversionContext conversionContext, ContentEntityObject page, Signature2 signature, boolean protectedContent) { + ConfluenceUser currentUser = AuthenticatedUserThreadLocal.get(); + String currentUserName = currentUser.getName(); + boolean protectedContentAccess = protectedContent && (permissionManager.hasPermission(currentUser, Permission.EDIT, page) || signature.hasSigned(currentUserName)); - if (signature.isSignatureMissing(currentUserName)) { - context.put("signAs", contextHelper.getProfileNotNull(userManager, currentUserName).getFullName()); - context.put("signAction", bootstrapManager.getWebAppContextPath() + REST_PATH + "/sign"); - } - context.put("panel", getBoolean(params, "panel", true)); - context.put("protectedContent", protectedContentAccess); - if (protectedContentAccess && isPage(conversionContext)) { - context.put("protectedContentURL", bootstrapManager.getWebAppContextPath() + DISPLAY_PATH + "/" + ((Page) page).getSpaceKey() + "/" + signature.getProtectedKey()); - } + Map context = defaultVelocityContext(); + context.put("date", new DateTool()); + context.put("markdown", markdown); - boolean canExport = hideSignatures(params, signature, currentUserName); - Map signed = contextHelper.getProfiles(userManager, signature.getSignatures().keySet()); - Map missing = contextHelper.getProfiles(userManager, signature.getMissingSignatures()); - - context.put("orderedSignatures", contextHelper.getOrderedSignatures(signature)); - context.put("orderedMissingSignatureProfiles", contextHelper.getOrderedProfiles(userManager, signature.getMissingSignatures())); - context.put("profiles", contextHelper.union(signed, missing)); - context.put("signature", signature); - context.put("visibilityLimit", signature.getVisibilityLimit()); - context.put("mailtoSigned", getMailto(signed.values(), signature.getTitle(), true, signature)); - context.put("mailtoMissing", getMailto(missing.values(), signature.getTitle(), false, signature)); - context.put("UUID", UUID.randomUUID().toString().replace("-", "")); - context.put("downloadURL", canExport ? bootstrapManager.getWebAppContextPath() + REST_PATH + "/export?key=" + signature.getKey() : null); - return context; + if (signature.isSignatureMissing(currentUserName)) { + context.put("signAs", contextHelper.getProfileNotNull(userManager, currentUserName).getFullName()); + context.put("signAction", bootstrapManager.getWebAppContextPath() + REST_PATH + "/sign"); } - - private void ensureProtectedPage(ConversionContext conversionContext, Page page, Signature signature) { - Page protectedPage = pageManager.getPage(conversionContext.getSpaceKey(), signature.getProtectedKey()); - if (protectedPage == null) { - ContentPermissionSet editors = page.getContentPermissionSet(EDIT_PERMISSION); - if (editors == null || editors.size() == 0) { - throw new IllegalStateException("No editors found!"); - } - protectedPage = new Page(); - protectedPage.setSpace(page.getSpace()); - protectedPage.setParentPage(page); - protectedPage.setVersion(1); - protectedPage.setCreator(page.getCreator()); - for (ContentPermission editor : editors) { - protectedPage.addPermission(createUserPermission(EDIT_PERMISSION, editor.getUserSubject())); - protectedPage.addPermission(createUserPermission(VIEW_PERMISSION, editor.getUserSubject())); - } - for (String signedUserName : signature.getSignatures().keySet()) { - protectedPage.addPermission(createUserPermission(VIEW_PERMISSION, signedUserName)); - } - protectedPage.setTitle(signature.getProtectedKey()); - pageManager.saveContentEntity(protectedPage, DefaultSaveContext.DEFAULT); - page.addChild(protectedPage); - } + context.put("panel", getBoolean(params, "panel", true)); + context.put("protectedContent", protectedContentAccess); + if (protectedContentAccess && isPage(conversionContext)) { + context.put("protectedContentURL", bootstrapManager.getWebAppContextPath() + DISPLAY_PATH + "/" + ((Page) page).getSpaceKey() + "/" + signature.getProtectedKey()); } - private boolean hideSignatures(Map params, Signature signature, String currentUserName) { - try { - signature = signature.clone(); - } catch (CloneNotSupportedException e) { - throw new IllegalStateException(e); - } - boolean pendingVisible = isVisible(signature, currentUserName, params.get("pendingVisible")); - boolean signaturesVisible = isVisible(signature, currentUserName, params.get("signaturesVisible")); - if (!pendingVisible) signature.setMissingSignatures(new TreeSet<>()); - if (!signaturesVisible) signature.setSignatures(new HashMap<>()); - return pendingVisible && signaturesVisible; + boolean canExport = hideSignatures(params, signature, currentUserName); + Map signed = contextHelper.getProfiles(userManager, signature.getSignatures().keySet()); + Map missing = contextHelper.getProfiles(userManager, signature.getMissingSignatures()); + + context.put("orderedSignatures", contextHelper.getOrderedSignatures(signature)); + context.put("orderedMissingSignatureProfiles", contextHelper.getOrderedProfiles(userManager, signature.getMissingSignatures())); + context.put("profiles", contextHelper.union(signed, missing)); + context.put("signature", signature); + context.put("visibilityLimit", signature.getVisibilityLimit()); + context.put("mailtoSigned", getMailto(signed.values(), signature.getTitle(), true, signature)); + context.put("mailtoMissing", getMailto(missing.values(), signature.getTitle(), false, signature)); + context.put("UUID", UUID.randomUUID().toString().replace("-", "")); + context.put("downloadURL", canExport ? bootstrapManager.getWebAppContextPath() + REST_PATH + "/export?key=" + signature.getKey() : null); + return context; + } + + private void ensureProtectedPage(ConversionContext conversionContext, Page page, Signature2 signature) { + Page protectedPage = pageManager.getPage(conversionContext.getSpaceKey(), signature.getProtectedKey()); + if (protectedPage == null) { + ContentPermissionSet editors = page.getContentPermissionSet(EDIT_PERMISSION); + if (editors == null || editors.size() == 0) { + throw new IllegalStateException("No editors found!"); + } + protectedPage = new Page(); + protectedPage.setSpace(page.getSpace()); + protectedPage.setParentPage(page); + protectedPage.setVersion(1); + protectedPage.setCreator(page.getCreator()); + for (ContentPermission editor : editors) { + protectedPage.addPermission(createUserPermission(EDIT_PERMISSION, editor.getUserSubject())); + protectedPage.addPermission(createUserPermission(VIEW_PERMISSION, editor.getUserSubject())); + } + for (String signedUserName : signature.getSignatures().keySet()) { + protectedPage.addPermission(createUserPermission(VIEW_PERMISSION, signedUserName)); + } + protectedPage.setTitle(signature.getProtectedKey()); + pageManager.saveContentEntity(protectedPage, DefaultSaveContext.DEFAULT); + page.addChild(protectedPage); } - - private boolean isVisible(Signature signature, String currentUserName, String signaturesVisibleParam) { - switch (SignaturesVisible.ofValue(signaturesVisibleParam)) { - case IF_SIGNATORY: - return signature.hasSigned(currentUserName) || signature.isSignatory(currentUserName); - case IF_SIGNED: - return signature.hasSigned(currentUserName); - case ALWAYS: - return true; - default: - throw new InvalidParameterException(String.format("'%s' is an unknown value of SignaturesVisible!", signaturesVisibleParam)); - } - } - - private boolean isPage(ConversionContext conversionContext) { - return conversionContext.getEntity() instanceof Page; + } + + private boolean hideSignatures(Map params, Signature2 signature, String currentUserName) { + boolean pendingVisible = isVisible(signature, currentUserName, params.get("pendingVisible")); + boolean signaturesVisible = isVisible(signature, currentUserName, params.get("signaturesVisible")); + if (!pendingVisible) signature.setMissingSignatures(new TreeSet<>()); + if (!signaturesVisible) signature.setSignatures(new HashMap<>()); + return pendingVisible && signaturesVisible; + } + + private boolean isVisible(Signature2 signature, String currentUserName, String signaturesVisibleParam) { + switch (SignaturesVisible.ofValue(signaturesVisibleParam)) { + case IF_SIGNATORY: + return signature.hasSigned(currentUserName) || signature.isSignatory(currentUserName); + case IF_SIGNED: + return signature.hasSigned(currentUserName); + case ALWAYS: + return true; + default: + throw new InvalidParameterException(String.format("'%s' is an unknown value of SignaturesVisible!", signaturesVisibleParam)); } - - private String warning(String message) { - return "
\n" + - "

\n" + - " " + i18nResolver.getText("com.baloise.confluence.digital-signature.signature.label") + "\n" + - "

\n" + - "

" + message + "

\n" + - "
"; + } + + private boolean isPage(ConversionContext conversionContext) { + return conversionContext.getEntity() instanceof Page; + } + + private String warning(String message) { + return "
\n" + "

\n" + " " + i18nResolver.getText("com.baloise.confluence.digital-signature.signature.label") + "\n" + "

\n" + "

" + message + "

\n" + "
"; + } + + private Set loadInheritedSigners(InheritSigners inheritSigners, ConversionContext conversionContext) { + Set users = new HashSet<>(); + switch (inheritSigners) { + case READERS_AND_WRITERS: + users.addAll(loadUsers(conversionContext, VIEW_PERMISSION)); + users.addAll(loadUsers(conversionContext, EDIT_PERMISSION)); + break; + case READERS_ONLY: + users.addAll(loadUsers(conversionContext, VIEW_PERMISSION)); + users.removeAll(loadUsers(conversionContext, EDIT_PERMISSION)); + break; + case WRITERS_ONLY: + users.addAll(loadUsers(conversionContext, EDIT_PERMISSION)); + break; + case NONE: + break; + default: + throw new IllegalArgumentException(inheritSigners + " is unknown or not yet implemented!"); } - - private Set loadInheritedSigners(InheritSigners inheritSigners, ConversionContext conversionContext) { - Set users = new HashSet<>(); - switch (inheritSigners) { - case READERS_AND_WRITERS: - users.addAll(loadUsers(conversionContext, VIEW_PERMISSION)); - users.addAll(loadUsers(conversionContext, EDIT_PERMISSION)); - break; - case READERS_ONLY: - users.addAll(loadUsers(conversionContext, VIEW_PERMISSION)); - users.removeAll(loadUsers(conversionContext, EDIT_PERMISSION)); - break; - case WRITERS_ONLY: - users.addAll(loadUsers(conversionContext, EDIT_PERMISSION)); - break; - case NONE: - break; - default: - throw new IllegalArgumentException(inheritSigners + " is unknown or not yet implemented!"); + return users; + } + + private Set loadUsers(ConversionContext conversionContext, String permission) { + Set users = new HashSet<>(); + ContentPermissionSet contentPermissionSet = conversionContext.getEntity().getContentPermissionSet(permission); + if (contentPermissionSet != null) { + for (ContentPermission cp : contentPermissionSet) { + if (cp.getGroupName() != null) { + users.addAll(loadUserGroup(cp.getGroupName())); } - return users; - } - - private Set loadUsers(ConversionContext conversionContext, String permission) { - Set users = new HashSet<>(); - ContentPermissionSet contentPermissionSet = conversionContext.getEntity().getContentPermissionSet(permission); - if (contentPermissionSet != null) { - for (ContentPermission cp : contentPermissionSet) { - if (cp.getGroupName() != null) { - users.addAll(loadUserGroup(cp.getGroupName())); - } - if (cp.getUserSubject() != null) { - users.add(cp.getUserSubject().getName()); - } - } - } - return users; - } - - private Set loadUserGroups(Iterable groupNames) { - Set ret = new HashSet<>(); - for (String groupName : groupNames) { - ret.addAll(loadUserGroup(groupName)); - } - return ret; - } - - private Set loadUserGroup(String groupName) { - Set ret = new HashSet<>(); - try { - if (groupName == null) return ret; - Group group = groupManager.getGroup(groupName.trim()); - if (group == null) return ret; - Pager pager = groupManager.getMemberNames(group); - while (!pager.onLastPage()) { - ret.addAll(pager.getCurrentPage()); - pager.nextPage(); - } - ret.addAll(pager.getCurrentPage()); - } catch (EntityException e) { - e.printStackTrace(); + if (cp.getUserSubject() != null) { + users.add(cp.getUserSubject().getName()); } - return ret; + } } + return users; + } - private Boolean getBoolean(Map params, String key, Boolean fallback) { - String value = params.get(key); - return value == null ? fallback : Boolean.valueOf(value); + private Set loadUserGroups(Iterable groupNames) { + Set ret = new HashSet<>(); + for (String groupName : groupNames) { + ret.addAll(loadUserGroup(groupName)); } - - private long getLong(Map params, String key, long fallback) { - String value = params.get(key); - return value == null ? fallback : Long.parseLong(value); + return ret; + } + + private Set loadUserGroup(String groupName) { + Set ret = new HashSet<>(); + try { + if (groupName == null) return ret; + Group group = groupManager.getGroup(groupName.trim()); + if (group == null) return ret; + Pager pager = groupManager.getMemberNames(group); + while (!pager.onLastPage()) { + ret.addAll(pager.getCurrentPage()); + pager.nextPage(); + } + ret.addAll(pager.getCurrentPage()); + } catch (EntityException e) { + e.printStackTrace(); } - - private Set getSet(Map params, String key) { - String value = params.get(key); - return value == null || value.trim().isEmpty() ? new TreeSet<>() : new TreeSet<>(asList(value.split("[;, ]+"))); + return ret; + } + + private Boolean getBoolean(Map params, String key, Boolean fallback) { + String value = params.get(key); + return value == null ? fallback : Boolean.valueOf(value); + } + + private long getLong(Map params, String key, long fallback) { + String value = params.get(key); + return value == null ? fallback : Long.parseLong(value); + } + + private Set getSet(Map params, String key) { + String value = params.get(key); + return value == null || value.trim().isEmpty() ? new TreeSet<>() : new TreeSet<>(asList(value.split("[;, ]+"))); + } + + private Signature2 sync(Signature2 signature, Set signers) { + Signature2 loaded = Signature2.fromBandana(this.bandanaManager, signature.getKey()); + if (loaded != null) { + signature.setSignatures(loaded.getSignatures()); + boolean save = false; + + if (!Objects.equals(loaded.getNotify(), signature.getNotify())) { + loaded.setNotify(signature.getNotify()); + save = true; + } + + signers.removeAll(loaded.getSignatures().keySet()); + signature.setMissingSignatures(signers); + if (!Objects.equals(loaded.getMissingSignatures(), signature.getMissingSignatures())) { + loaded.setMissingSignatures(signature.getMissingSignatures()); + save = true; + } + + if (loaded.getMaxSignatures() != signature.getMaxSignatures()) { + loaded.setMaxSignatures(signature.getMaxSignatures()); + save = true; + } + + if (loaded.getVisibilityLimit() != signature.getVisibilityLimit()) { + loaded.setVisibilityLimit(signature.getVisibilityLimit()); + save = true; + } + + if (save) { + save(loaded); + } + } else { + signature.setMissingSignatures(signers); + save(signature); } + return signature; + } - private Signature sync(Signature signature, Set signers) { - Signature loaded = (Signature) bandanaManager.getValue(GLOBAL_CONTEXT, signature.getKey()); - if (loaded != null) { - signature.setSignatures(loaded.getSignatures()); - boolean save = false; - - if (!Objects.equals(loaded.getNotify(), signature.getNotify())) { - loaded.setNotify(signature.getNotify()); - save = true; - } - - signers.removeAll(loaded.getSignatures().keySet()); - signature.setMissingSignatures(signers); - if (!Objects.equals(loaded.getMissingSignatures(), signature.getMissingSignatures())) { - loaded.setMissingSignatures(signature.getMissingSignatures()); - save = true; - } - - if (loaded.getMaxSignatures() != signature.getMaxSignatures()) { - loaded.setMaxSignatures(signature.getMaxSignatures()); - save = true; - } - - if (loaded.getVisibilityLimit() != signature.getVisibilityLimit()) { - loaded.setVisibilityLimit(signature.getVisibilityLimit()); - save = true; - } - - if (save) save(loaded); - } else { - signature.setMissingSignatures(signers); - save(signature); - } - return signature; + private void save(Signature2 signature) { + if (signature.hasMissingSignatures()) { + Signature2.toBandana(bandanaManager, signature); } - - private void save(Signature signature) { - if (signature.hasMissingSignatures()) - bandanaManager.setValue(GLOBAL_CONTEXT, signature.getKey(), signature); + } + + @Override + public BodyType getBodyType() { + return BodyType.PLAIN_TEXT; + } + + @Override + public OutputType getOutputType() { + return OutputType.BLOCK; + } + + protected String getMailto(Collection profiles, String subject, boolean signed, Signature2 signature) { + if (profiles == null || profiles.isEmpty()) return null; + Collection profilesWithMail = profiles.stream().filter(contextHelper::hasEmail).collect(toList()); + StringBuilder ret = new StringBuilder("mailto:"); + for (UserProfile profile : profilesWithMail) { + if (ret.length() > 7) ret.append(','); + ret.append(contextHelper.mailTo(profile)); } - - @Override - public BodyType getBodyType() { - return BodyType.PLAIN_TEXT; + ret.append("?Subject=").append(urlEncode(subject)); + if (ret.length() > MAX_MAILTO_CHARACTER_COUNT) { + ret.setLength(0); + ret.append("mailto:"); + for (UserProfile profile : profilesWithMail) { + if (ret.length() > 7) ret.append(','); + ret.append(profile.getEmail().trim()); + } + ret.append("?Subject=").append(urlEncode(subject)); } - - @Override - public OutputType getOutputType() { - return OutputType.BLOCK; + if (ret.length() > MAX_MAILTO_CHARACTER_COUNT) { + return bootstrapManager.getWebAppContextPath() + REST_PATH + "/emails?key=" + signature.getKey() + "&signed=" + signed; } - - protected String getMailto(Collection profiles, String subject, boolean signed, Signature signature) { - if (profiles == null || profiles.isEmpty()) return null; - Collection profilesWithMail = profiles.stream() - .filter(contextHelper::hasEmail) - .collect(toList()); - StringBuilder ret = new StringBuilder("mailto:"); - for (UserProfile profile : profilesWithMail) { - if (ret.length() > 7) ret.append(','); - ret.append(contextHelper.mailTo(profile)); - } - ret.append("?Subject=").append(urlEncode(subject)); - if (ret.length() > MAX_MAILTO_CHARACTER_COUNT) { - ret.setLength(0); - ret.append("mailto:"); - for (UserProfile profile : profilesWithMail) { - if (ret.length() > 7) ret.append(','); - ret.append(profile.getEmail().trim()); - } - ret.append("?Subject=").append(urlEncode(subject)); - } - if (ret.length() > MAX_MAILTO_CHARACTER_COUNT) { - return bootstrapManager.getWebAppContextPath() + REST_PATH + "/emails?key=" + signature.getKey() + "&signed=" + signed; - } - return ret.toString(); - } - - public String urlEncode(String string) { - try { - return URLEncoder.encode(string, "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new IllegalStateException(e); - } + return ret.toString(); + } + + public String urlEncode(String string) { + try { + return URLEncoder.encode(string, StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + throw new IllegalStateException(e); } + } } diff --git a/src/main/java/com/baloise/confluence/digitalsignature/InheritSigners.java b/src/main/java/com/baloise/confluence/digitalsignature/InheritSigners.java index c603032..7694e53 100644 --- a/src/main/java/com/baloise/confluence/digitalsignature/InheritSigners.java +++ b/src/main/java/com/baloise/confluence/digitalsignature/InheritSigners.java @@ -1,16 +1,16 @@ package com.baloise.confluence.digitalsignature; public enum InheritSigners { - NONE, - READERS_AND_WRITERS, - READERS_ONLY, - WRITERS_ONLY; + NONE, + READERS_AND_WRITERS, + READERS_ONLY, + WRITERS_ONLY; - public static InheritSigners ofValue(String v) { - try { - return InheritSigners.valueOf(v.toUpperCase().replaceAll("\\W+", "_")); - } catch (Exception e) { - return NONE; - } + public static InheritSigners ofValue(String v) { + try { + return InheritSigners.valueOf(v.toUpperCase().replaceAll("\\W+", "_")); + } catch (Exception e) { + return NONE; } + } } diff --git a/src/main/java/com/baloise/confluence/digitalsignature/Markdown.java b/src/main/java/com/baloise/confluence/digitalsignature/Markdown.java index abf2f99..ee3199b 100644 --- a/src/main/java/com/baloise/confluence/digitalsignature/Markdown.java +++ b/src/main/java/com/baloise/confluence/digitalsignature/Markdown.java @@ -5,19 +5,19 @@ import com.vladsch.flexmark.util.data.MutableDataSet; public class Markdown { - private final Parser parser; - private final HtmlRenderer renderer; + private final Parser parser; + private final HtmlRenderer renderer; - public Markdown() { - MutableDataSet options = new MutableDataSet(); - options.set(HtmlRenderer.DO_NOT_RENDER_LINKS, true); - options.set(HtmlRenderer.ESCAPE_HTML, true); + public Markdown() { + MutableDataSet options = new MutableDataSet(); + options.set(HtmlRenderer.DO_NOT_RENDER_LINKS, true); + options.set(HtmlRenderer.ESCAPE_HTML, true); - parser = Parser.builder(options).build(); - renderer = HtmlRenderer.builder(options).build(); - } + parser = Parser.builder(options).build(); + renderer = HtmlRenderer.builder(options).build(); + } - public String toHTML(String markdown) { - return renderer.render(parser.parse(markdown)); - } + public String toHTML(String markdown) { + return renderer.render(parser.parse(markdown)); + } } diff --git a/src/main/java/com/baloise/confluence/digitalsignature/Signature.java b/src/main/java/com/baloise/confluence/digitalsignature/Signature.java index 57e23ae..c30face 100644 --- a/src/main/java/com/baloise/confluence/digitalsignature/Signature.java +++ b/src/main/java/com/baloise/confluence/digitalsignature/Signature.java @@ -9,6 +9,13 @@ import java.util.Set; import java.util.TreeSet; +/** + * This class is deprecated and should no longer be used except for downwards compatibility, i.e. reading values from + * Bandana that were written with an older version. + *
+ * Use @{@link com.baloise.confluence.digitalsignature.Signature2} instead. + */ +@Deprecated public class Signature implements Serializable, Cloneable { private static final long serialVersionUID = 1L; @@ -195,9 +202,9 @@ public boolean isSignatory(String userName) { public boolean hasMissingSignatures() { return !isMaxSignaturesReached() && (isPetitionMode() || !getMissingSignatures().isEmpty()); } - + @Override - public Signature clone() throws CloneNotSupportedException{ - return (Signature) super.clone(); + public Signature clone() throws CloneNotSupportedException{ + return (Signature) super.clone(); } } diff --git a/src/main/java/com/baloise/confluence/digitalsignature/Signature2.java b/src/main/java/com/baloise/confluence/digitalsignature/Signature2.java new file mode 100644 index 0000000..f57d350 --- /dev/null +++ b/src/main/java/com/baloise/confluence/digitalsignature/Signature2.java @@ -0,0 +1,188 @@ +package com.baloise.confluence.digitalsignature; + +import com.atlassian.bandana.BandanaManager; +import com.google.common.collect.Sets; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.TreeSet; + +import static com.atlassian.confluence.setup.bandana.ConfluenceBandanaContext.GLOBAL_CONTEXT; +import static org.apache.commons.codec.digest.DigestUtils.sha256Hex; + +@Slf4j +@Getter +@Setter +@NoArgsConstructor +public class Signature2 implements Serializable { + public static final Gson GSON = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ssz").create(); + private static final long serialVersionUID = 1L; + private String key = ""; + private String hash = ""; + private long pageId; + private String title = ""; + private String body = ""; + private long maxSignatures = -1; + private long visibilityLimit = -1; + private Map signatures = new HashMap<>(); + private Set missingSignatures = new TreeSet<>(); + private Set notify = new TreeSet<>(); + + public Signature2(long pageId, String body, String title) { + this.pageId = pageId; + this.body = body; + this.title = title == null ? "" : title; + this.hash = sha256Hex(pageId + ":" + title + ":" + body); + this.key = "signature." + hash; + } + + public static boolean isPetitionMode(Set userGroups) { + return userGroups != null + && userGroups.size() == 1 + && userGroups.iterator().next().trim().equals("*"); + } + + static Signature2 deserialize(String serialization) { + return GSON.fromJson(serialization, Signature2.class); + } + + public static Signature2 fromBandana(BandanaManager mgr, String key) { + if (mgr.getKeys(GLOBAL_CONTEXT) == null + || !Sets.newHashSet(mgr.getKeys(GLOBAL_CONTEXT)).contains(key)) { + return null; + } + + Object value = mgr.getValue(GLOBAL_CONTEXT, key); + + if (value == null) { + throw new IllegalArgumentException("Value is null in Bandana???"); + } + + if (value instanceof Signature) { + // required for downward compatibility - update for next time. + Signature signature = (Signature) value; + Signature2 sig = new Signature2(signature.getPageId(), signature.getBody(), signature.getTitle()); + sig.setSignatures(signature.getSignatures()); + sig.setMaxSignatures(signature.getMaxSignatures()); + sig.setHash(signature.getHash()); + sig.setKey(signature.getKey()); + sig.setNotify(signature.getNotify()); + sig.setMissingSignatures(signature.getMissingSignatures()); + sig.setVisibilityLimit(signature.getVisibilityLimit()); + toBandana(mgr, key, sig); + return sig; + } + + if (value instanceof String) { + try { + return deserialize((String) value); + } catch (Exception e) { + log.error("Could not deserialize String value from Bandana", e); + return null; + } + } + + throw new IllegalArgumentException(String.format("Could not deserialize %s value from Bandana. Please clear the plugin-cache and reboot confluence. (https://github.com/baloise/digital-signature/issues/82)", value)); + } + + public static void toBandana(BandanaManager mgr, String key, Signature2 sig) { + mgr.setValue(GLOBAL_CONTEXT, key, sig.serialize()); + } + + public static void toBandana(BandanaManager mgr, Signature2 sig) { + toBandana(mgr, sig.getKey(), sig); + } + + String serialize() { + return GSON.toJson(this, Signature2.class); + } + + public String getHash() { + if (hash == null) { + hash = getKey().replace("signature.", ""); + } + return hash; + } + + public String getProtectedKey() { + return "protected." + getHash(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((key == null) ? 0 : key.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + + return Objects.equals(key, ((Signature2) obj).key); + } + + public Signature2 withNotified(Set notified) { + this.notify = notified; + return this; + } + + public Signature2 withMaxSignatures(long maxSignatures) { + this.maxSignatures = maxSignatures; + return this; + } + + public Signature2 withVisibilityLimit(long visibilityLimit) { + this.visibilityLimit = visibilityLimit; + return this; + } + + public boolean hasSigned(String userName) { + return signatures.containsKey(userName); + } + + public boolean isPetitionMode() { + return isPetitionMode(getMissingSignatures()); + } + + public boolean sign(String userName) { + if (!isMaxSignaturesReached() && !isPetitionMode() && !getMissingSignatures().remove(userName)) { + return false; + } + + getSignatures().put(userName, new Date()); + return true; + } + + public boolean isMaxSignaturesReached() { + return maxSignatures > -1 && maxSignatures <= getSignatures().size(); + } + + public boolean isSignatureMissing(String userName) { + return !isMaxSignaturesReached() && !hasSigned(userName) && isSignatory(userName); + } + + public boolean isSignatory(String userName) { + return isPetitionMode() || getMissingSignatures().contains(userName); + } + + public boolean hasMissingSignatures() { + return !isMaxSignaturesReached() && (isPetitionMode() || !getMissingSignatures().isEmpty()); + } +} diff --git a/src/main/java/com/baloise/confluence/digitalsignature/SignaturesVisible.java b/src/main/java/com/baloise/confluence/digitalsignature/SignaturesVisible.java index 417b28b..103d213 100644 --- a/src/main/java/com/baloise/confluence/digitalsignature/SignaturesVisible.java +++ b/src/main/java/com/baloise/confluence/digitalsignature/SignaturesVisible.java @@ -1,15 +1,15 @@ package com.baloise.confluence.digitalsignature; public enum SignaturesVisible { - ALWAYS, - IF_SIGNATORY, - IF_SIGNED; + ALWAYS, + IF_SIGNATORY, + IF_SIGNED; - public static SignaturesVisible ofValue(String v) { - try { - return SignaturesVisible.valueOf(v.toUpperCase().replaceAll("\\W+", "_")); - } catch (Exception e) { - return ALWAYS; - } + public static SignaturesVisible ofValue(String v) { + try { + return SignaturesVisible.valueOf(v.toUpperCase().replaceAll("\\W+", "_")); + } catch (Exception e) { + return ALWAYS; } + } } diff --git a/src/main/java/com/baloise/confluence/digitalsignature/UserProfileByName.java b/src/main/java/com/baloise/confluence/digitalsignature/UserProfileByName.java index ea02d57..1687b06 100644 --- a/src/main/java/com/baloise/confluence/digitalsignature/UserProfileByName.java +++ b/src/main/java/com/baloise/confluence/digitalsignature/UserProfileByName.java @@ -5,22 +5,21 @@ import java.util.Comparator; public class UserProfileByName implements Comparator { + @Override + public int compare(UserProfile u1, UserProfile u2) { + int ret = nn(u1.getFullName()).compareTo(nn(u2.getFullName())); + if (ret != 0) return ret; - @Override - public int compare(UserProfile u1, UserProfile u2) { - int ret = nn(u1.getFullName()).compareTo(nn(u2.getFullName())); - if (ret != 0) return ret; + ret = nn(u1.getEmail()).compareTo(nn(u2.getEmail())); + if (ret != 0) return ret; - ret = nn(u1.getEmail()).compareTo(nn(u2.getEmail())); - if (ret != 0) return ret; + ret = nn(u1.getUsername()).compareTo(nn(u2.getUsername())); + if (ret != 0) return ret; - ret = nn(u1.getUsername()).compareTo(nn(u2.getUsername())); - if (ret != 0) return ret; + return Integer.compare(u1.hashCode(), u2.hashCode()); + } - return Integer.compare(u1.hashCode(), u2.hashCode()); - } - - private String nn(String string) { - return string == null ? "" : string; - } + private String nn(String string) { + return string == null ? "" : string; + } } diff --git a/src/main/java/com/baloise/confluence/digitalsignature/api/DigitalSignatureComponent.java b/src/main/java/com/baloise/confluence/digitalsignature/api/DigitalSignatureComponent.java index 3825a8e..41dda06 100644 --- a/src/main/java/com/baloise/confluence/digitalsignature/api/DigitalSignatureComponent.java +++ b/src/main/java/com/baloise/confluence/digitalsignature/api/DigitalSignatureComponent.java @@ -1,7 +1,7 @@ package com.baloise.confluence.digitalsignature.api; public interface DigitalSignatureComponent { - String PLUGIN_KEY = "com.baloise.confluence:digital-signature"; + String PLUGIN_KEY = "com.baloise.confluence:digital-signature"; - String getName(); + String getName(); } diff --git a/src/main/java/com/baloise/confluence/digitalsignature/impl/DigitalSignatureComponentImpl.java b/src/main/java/com/baloise/confluence/digitalsignature/impl/DigitalSignatureComponentImpl.java index 379b812..4a49a44 100644 --- a/src/main/java/com/baloise/confluence/digitalsignature/impl/DigitalSignatureComponentImpl.java +++ b/src/main/java/com/baloise/confluence/digitalsignature/impl/DigitalSignatureComponentImpl.java @@ -11,23 +11,23 @@ @ExportAsService({DigitalSignatureComponent.class}) @Named("digitalSignatureComponent") public class DigitalSignatureComponentImpl implements DigitalSignatureComponent { - @ComponentImport - private final ApplicationProperties applicationProperties; + @ComponentImport + private final ApplicationProperties applicationProperties; - public DigitalSignatureComponentImpl() { - this(null); - } - - @Inject - public DigitalSignatureComponentImpl(final ApplicationProperties applicationProperties) { - this.applicationProperties = applicationProperties; - } + public DigitalSignatureComponentImpl() { + this(null); + } - public String getName() { - if (null != applicationProperties) { - return "digitalSignatureComponent:" + applicationProperties.getDisplayName(); - } + @Inject + public DigitalSignatureComponentImpl(final ApplicationProperties applicationProperties) { + this.applicationProperties = applicationProperties; + } - return "digitalSignatureComponent"; + public String getName() { + if (null != applicationProperties) { + return "digitalSignatureComponent:" + applicationProperties.getDisplayName(); } + + return "digitalSignatureComponent"; + } } diff --git a/src/main/java/com/baloise/confluence/digitalsignature/rest/DigitalSigatureService.java b/src/main/java/com/baloise/confluence/digitalsignature/rest/DigitalSigatureService.java deleted file mode 100644 index 433bca9..0000000 --- a/src/main/java/com/baloise/confluence/digitalsignature/rest/DigitalSigatureService.java +++ /dev/null @@ -1,212 +0,0 @@ -package com.baloise.confluence.digitalsignature.rest; - -import com.atlassian.bandana.BandanaManager; -import com.atlassian.confluence.pages.Page; -import com.atlassian.confluence.pages.PageManager; -import com.atlassian.confluence.setup.settings.SettingsManager; -import com.atlassian.confluence.user.AuthenticatedUserThreadLocal; -import com.atlassian.confluence.user.ConfluenceUser; -import com.atlassian.confluence.velocity.htmlsafe.HtmlSafe; -import com.atlassian.mail.Email; -import com.atlassian.mail.MailException; -import com.atlassian.mail.server.MailServerManager; -import com.atlassian.mail.server.SMTPMailServer; -import com.atlassian.mywork.model.NotificationBuilder; -import com.atlassian.mywork.service.LocalNotificationService; -import com.atlassian.plugin.spring.scanner.annotation.component.Scanned; -import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport; -import com.atlassian.sal.api.message.I18nResolver; -import com.atlassian.sal.api.user.UserManager; -import com.atlassian.sal.api.user.UserProfile; -import com.baloise.confluence.digitalsignature.ContextHelper; -import com.baloise.confluence.digitalsignature.Markdown; -import com.baloise.confluence.digitalsignature.Signature; -import org.apache.velocity.tools.generic.DateTool; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.ws.rs.Consumes; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriInfo; -import java.net.URI; -import java.text.MessageFormat; -import java.util.Date; -import java.util.Map; -import java.util.concurrent.ExecutionException; -import java.util.function.Function; - -import static com.atlassian.confluence.renderer.radeox.macros.MacroUtils.defaultVelocityContext; -import static com.atlassian.confluence.security.ContentPermission.VIEW_PERMISSION; -import static com.atlassian.confluence.security.ContentPermission.createUserPermission; -import static com.atlassian.confluence.setup.bandana.ConfluenceBandanaContext.GLOBAL_CONTEXT; -import static com.atlassian.confluence.util.velocity.VelocityUtils.getRenderedTemplate; -import static com.baloise.confluence.digitalsignature.api.DigitalSignatureComponent.PLUGIN_KEY; -import static java.lang.String.format; -import static java.net.URI.create; -import static java.util.stream.Collectors.toList; -import static javax.ws.rs.core.Response.status; -import static javax.ws.rs.core.Response.temporaryRedirect; - -@Path("/") -@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) -@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) -@Scanned -public class DigitalSigatureService { - private static final Logger log = LoggerFactory.getLogger(DigitalSigatureService.class); - private final BandanaManager bandanaManager; - private final SettingsManager settingsManager; - private final UserManager userManager; - private final LocalNotificationService notificationService; - private final MailServerManager mailServerManager; - private final ContextHelper contextHelper = new ContextHelper(); - private final transient Markdown markdown = new Markdown(); - private final PageManager pageManager; - private I18nResolver i18nResolver; - - public DigitalSigatureService( - @ComponentImport BandanaManager bandanaManager, - @ComponentImport SettingsManager settingsManager, - @ComponentImport UserManager userManager, - @ComponentImport LocalNotificationService notificationService, - @ComponentImport MailServerManager mailServerManager, - @ComponentImport PageManager pageManager, - @ComponentImport I18nResolver i18nResolver - ) { - this.settingsManager = settingsManager; - this.bandanaManager = bandanaManager; - this.notificationService = notificationService; - this.userManager = userManager; - this.mailServerManager = mailServerManager; - this.pageManager = pageManager; - this.i18nResolver = i18nResolver; - } - - @GET - @Path("sign") - public Response sign(@QueryParam("key") final String key, @Context UriInfo uriInfo) { - ConfluenceUser confluenceUser = AuthenticatedUserThreadLocal.get(); - String userName = confluenceUser.getName(); - Signature signature = (Signature) bandanaManager.getValue(GLOBAL_CONTEXT, key); - if (!signature.sign(userName)) { - status(Response.Status.BAD_REQUEST) - .entity(i18nResolver.getText("com.baloise.confluence.digital-signature.signature.service.error.badUser", userName, key)) - .type(MediaType.TEXT_PLAIN) - .build(); - } - bandanaManager.setValue(GLOBAL_CONTEXT, key, signature); - - String baseUrl = settingsManager.getGlobalSettings().getBaseUrl(); - for (String notifiedUser : signature.getNotify()) { - notify(notifiedUser, confluenceUser, signature, baseUrl); - } - Page parentPage = pageManager.getPage(signature.getPageId()); - Page protectedPage = pageManager.getPage(parentPage.getSpaceKey(), signature.getProtectedKey()); - if (protectedPage != null) { - protectedPage.addPermission(createUserPermission(VIEW_PERMISSION, confluenceUser)); - pageManager.saveContentEntity(protectedPage, null); - } - URI pageUri = create(settingsManager.getGlobalSettings().getBaseUrl() + "/pages/viewpage.action?pageId=" + signature.getPageId()); - return temporaryRedirect(pageUri).build(); - } - - private void notify(final String notifiedUser, ConfluenceUser signedUser, final Signature signature, final String baseUrl) { - try { - UserProfile notifiedUserProfile = contextHelper.getProfileNotNull(userManager, notifiedUser); - - String user = format("%s", - baseUrl + "/display/~" + signedUser.getName(), - signedUser.getFullName() - ); - String document = format("%s", - baseUrl + "/pages/viewpage.action?pageId=" + signature.getPageId(), - signature.getTitle() - ); - String html = i18nResolver.getText("com.baloise.confluence.digital-signature.signature.service.message.hasSignedShort", user, document); - if (signature.isMaxSignaturesReached()) { - html = html + "
" + i18nResolver.getText("com.baloise.confluence.digital-signature.signature.service.warning.maxSignaturesReached", signature.getMaxSignatures()); - } - String titleText = i18nResolver.getText("com.baloise.confluence.digital-signature.signature.service.message.hasSignedShort", signedUser.getFullName(), signature.getTitle()); - - notificationService.createOrUpdate(notifiedUser, new NotificationBuilder() - .application(PLUGIN_KEY) // a unique key that identifies your plugin - .title(titleText) - .itemTitle(titleText) - .description(html) - .groupingId(PLUGIN_KEY + "-signature") // a key to aggregate notifications - .createNotification()).get(); - - SMTPMailServer mailServer = mailServerManager.getDefaultSMTPMailServer(); - - if (mailServer == null) { - log.warn("No default SMTP server found -> no signature notification sent."); - } else if (!contextHelper.hasEmail(notifiedUserProfile)) { - log.warn(notifiedUser + " is to be notified but has no email address. Skipping email notification"); - } else { - mailServer.send( - new Email(notifiedUserProfile.getEmail()) - .setSubject(titleText) - .setBody(html) - .setMimeType("text/html") - ); - } - } catch (IllegalArgumentException | InterruptedException | MailException | ExecutionException e) { - log.error("Could not send notification to " + notifiedUser, e); - } - } - - @GET - @Path("export") - @Produces("text/html; charset=UTF-8") - @HtmlSafe - public String export(@QueryParam("key") final String key) { - Signature signature = (Signature) bandanaManager.getValue(GLOBAL_CONTEXT, key); - - Map signed = contextHelper.getProfiles(userManager, signature.getSignatures().keySet()); - Map missing = contextHelper.getProfiles(userManager, signature.getMissingSignatures()); - - Map context = defaultVelocityContext(); - context.put("markdown", markdown); - context.put("orderedSignatures", contextHelper.getOrderedSignatures(signature)); - context.put("orderedMissingSignatureProfiles", contextHelper.getOrderedProfiles(userManager, signature.getMissingSignatures())); - context.put("profiles", contextHelper.union(signed, missing)); - context.put("signature", signature); - context.put("currentDate", new Date()); - context.put("date", new DateTool()); - - return getRenderedTemplate("templates/export.vm", context); - } - - @GET - @Path("emails") - @Produces("text/html; charset=UTF-8") - public Response emails(@QueryParam("key") final String key, @QueryParam("signed") final boolean signed, @QueryParam("emailOnly") final boolean emailOnly, @Context UriInfo uriInfo) { - Signature signature = (Signature) bandanaManager.getValue(GLOBAL_CONTEXT, key); - Map profiles = contextHelper.getProfiles(userManager, signed ? signature.getSignatures().keySet() : signature.getMissingSignatures()); - - Map context = defaultVelocityContext(); - context.put("signature", signature); - String signatureText = format("%s ( %s )", signature.getTitle(), signature.getHash()); - String rawTemplate = signed ? - i18nResolver.getRawText("com.baloise.confluence.digital-signature.signature.service.message.signedUsersEmails") : - i18nResolver.getRawText("com.baloise.confluence.digital-signature.signature.service.message.unsignedUsersEmails"); - context.put("signedOrNotWithHtml", MessageFormat.format(rawTemplate, "", "", signatureText)); - context.put("withNamesChecked", emailOnly ? "" : "checked"); - context.put("signedChecked", signed ? "checked" : ""); - context.put("toggleWithNamesURL", uriInfo.getRequestUriBuilder().replaceQueryParam("emailOnly", !emailOnly).build()); - context.put("toggleSignedURL", uriInfo.getRequestUriBuilder().replaceQueryParam("signed", !signed).build()); - Function mapping = p -> (emailOnly ? p.getEmail() : contextHelper.mailTo(p)).trim(); - context.put("emails", profiles.values().stream() - .filter(contextHelper::hasEmail) - .map(mapping).collect(toList())); - - context.put("currentDate", new Date()); - context.put("date", new DateTool()); - return Response.ok(getRenderedTemplate("templates/email.vm", context)).build(); - } -} diff --git a/src/main/java/com/baloise/confluence/digitalsignature/rest/DigitalSignatureService.java b/src/main/java/com/baloise/confluence/digitalsignature/rest/DigitalSignatureService.java new file mode 100644 index 0000000..1feaf23 --- /dev/null +++ b/src/main/java/com/baloise/confluence/digitalsignature/rest/DigitalSignatureService.java @@ -0,0 +1,237 @@ +package com.baloise.confluence.digitalsignature.rest; + +import com.atlassian.bandana.BandanaManager; +import com.atlassian.confluence.pages.Page; +import com.atlassian.confluence.pages.PageManager; +import com.atlassian.confluence.setup.settings.SettingsManager; +import com.atlassian.confluence.user.AuthenticatedUserThreadLocal; +import com.atlassian.confluence.user.ConfluenceUser; +import com.atlassian.mail.Email; +import com.atlassian.mail.MailException; +import com.atlassian.mail.server.MailServerManager; +import com.atlassian.mail.server.SMTPMailServer; +import com.atlassian.mywork.model.NotificationBuilder; +import com.atlassian.mywork.service.LocalNotificationService; +import com.atlassian.plugin.spring.scanner.annotation.component.Scanned; +import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport; +import com.atlassian.sal.api.message.I18nResolver; +import com.atlassian.sal.api.user.UserManager; +import com.atlassian.sal.api.user.UserProfile; +import com.atlassian.velocity.htmlsafe.HtmlSafe; +import com.baloise.confluence.digitalsignature.ContextHelper; +import com.baloise.confluence.digitalsignature.Markdown; +import com.baloise.confluence.digitalsignature.Signature2; +import org.apache.velocity.tools.generic.DateTool; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; +import java.net.URI; +import java.text.MessageFormat; +import java.util.Date; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.function.Function; + +import static com.atlassian.confluence.renderer.radeox.macros.MacroUtils.defaultVelocityContext; +import static com.atlassian.confluence.security.ContentPermission.VIEW_PERMISSION; +import static com.atlassian.confluence.security.ContentPermission.createUserPermission; +import static com.atlassian.confluence.util.velocity.VelocityUtils.getRenderedTemplate; +import static com.baloise.confluence.digitalsignature.api.DigitalSignatureComponent.PLUGIN_KEY; +import static java.lang.String.format; +import static java.net.URI.create; +import static java.util.stream.Collectors.toList; +import static javax.ws.rs.core.Response.status; +import static javax.ws.rs.core.Response.temporaryRedirect; + +@Path("/") +@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) +@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) +@Scanned +public class DigitalSignatureService { + private static final Logger log = LoggerFactory.getLogger(DigitalSignatureService.class); + private final BandanaManager bandanaManager; + private final SettingsManager settingsManager; + private final UserManager userManager; + private final LocalNotificationService notificationService; + private final MailServerManager mailServerManager; + private final PageManager pageManager; + private final I18nResolver i18nResolver; + private final ContextHelper contextHelper = new ContextHelper(); + private final transient Markdown markdown = new Markdown(); + + public DigitalSignatureService(@ComponentImport BandanaManager bandanaManager, + @ComponentImport SettingsManager settingsManager, + @ComponentImport UserManager userManager, + @ComponentImport LocalNotificationService notificationService, + @ComponentImport MailServerManager mailServerManager, + @ComponentImport PageManager pageManager, + @ComponentImport I18nResolver i18nResolver) { + this.bandanaManager = bandanaManager; + this.settingsManager = settingsManager; + this.userManager = userManager; + this.notificationService = notificationService; + this.mailServerManager = mailServerManager; + this.pageManager = pageManager; + this.i18nResolver = i18nResolver; + } + + @GET + @Path("sign") + public Response sign(@QueryParam("key") final String key, + @Context UriInfo uriInfo) { + ConfluenceUser confluenceUser = AuthenticatedUserThreadLocal.get(); + String userName = confluenceUser.getName(); + Signature2 signature = Signature2.fromBandana(bandanaManager, key); + + if (signature == null || userName == null || userName.trim().isEmpty()) { + log.error("Both, a signature and a user name are required to call this method.", + new NullPointerException(signature == null ? "signature" : "userName")); + return Response.noContent().build(); + } + + if (!signature.sign(userName)) { + status(Response.Status.BAD_REQUEST) + .entity(i18nResolver.getText("com.baloise.confluence.digital-signature.signature.service.error.badUser", userName, key)) + .type(MediaType.TEXT_PLAIN) + .build(); + } + + Signature2.toBandana(bandanaManager, key, signature); + String baseUrl = settingsManager.getGlobalSettings().getBaseUrl(); + for (String notifiedUser : signature.getNotify()) { + notify(notifiedUser, confluenceUser, signature, baseUrl); + } + Page parentPage = pageManager.getPage(signature.getPageId()); + Page protectedPage = pageManager.getPage(parentPage.getSpaceKey(), signature.getProtectedKey()); + if (protectedPage != null) { + protectedPage.addPermission(createUserPermission(VIEW_PERMISSION, confluenceUser)); + pageManager.saveContentEntity(protectedPage, null); + } + + URI pageUri = create(settingsManager.getGlobalSettings().getBaseUrl() + "/pages/viewpage.action?pageId=" + signature.getPageId()); + return temporaryRedirect(pageUri).build(); + } + + private void notify(final String notifiedUser, ConfluenceUser signedUser, final Signature2 signature, final String baseUrl) { + try { + UserProfile notifiedUserProfile = contextHelper.getProfileNotNull(userManager, notifiedUser); + + String user = format("%s", + baseUrl, + signedUser.getName(), + signedUser.getFullName() + ); + String document = format("%s", + baseUrl, + signature.getPageId(), + signature.getTitle() + ); + String html = i18nResolver.getText("com.baloise.confluence.digital-signature.signature.service.message.hasSignedShort", user, document); + if (signature.isMaxSignaturesReached()) { + html += "
" + i18nResolver.getText("com.baloise.confluence.digital-signature.signature.service.warning.maxSignaturesReached", signature.getMaxSignatures()); + } + String titleText = i18nResolver.getText("com.baloise.confluence.digital-signature.signature.service.message.hasSignedShort", signedUser.getFullName(), signature.getTitle()); + + notificationService.createOrUpdate(notifiedUser, + new NotificationBuilder() + .application(PLUGIN_KEY) // a unique key that identifies your plugin + .title(titleText) + .itemTitle(titleText) + .description(html) + .groupingId(PLUGIN_KEY + "-signature") // a key to aggregate notifications + .createNotification()).get(); + + SMTPMailServer mailServer = mailServerManager.getDefaultSMTPMailServer(); + + if (mailServer == null) { + log.warn("No default SMTP server found -> no signature notification sent."); + } else if (!contextHelper.hasEmail(notifiedUserProfile)) { + log.warn(notifiedUser + " is to be notified but has no email address. Skipping email notification"); + } else { + mailServer.send( + new Email(notifiedUserProfile.getEmail()) + .setSubject(titleText) + .setBody(html) + .setMimeType("text/html") + ); + } + } catch (IllegalArgumentException | InterruptedException | MailException | ExecutionException e) { + log.error("Could not send notification to " + notifiedUser, e); + } + } + + @GET + @Path("export") + @Produces("text/html; charset=UTF-8") + @HtmlSafe + public String export(@QueryParam("key") final String key) { + Signature2 signature = Signature2.fromBandana(bandanaManager, key); + + if (signature == null) { + log.error("A signature is required to call this method.", new NullPointerException("signature")); + return "ERROR: A signature is required to call this method."; + } + + Map signed = contextHelper.getProfiles(userManager, signature.getSignatures().keySet()); + Map missing = contextHelper.getProfiles(userManager, signature.getMissingSignatures()); + + Map context = defaultVelocityContext(); + context.put("markdown", markdown); + context.put("orderedSignatures", contextHelper.getOrderedSignatures(signature)); + context.put("orderedMissingSignatureProfiles", contextHelper.getOrderedProfiles(userManager, signature.getMissingSignatures())); + context.put("profiles", contextHelper.union(signed, missing)); + context.put("signature", signature); + context.put("currentDate", new Date()); + context.put("date", new DateTool()); + + return getRenderedTemplate("templates/export.vm", context); + } + + @GET + @Path("emails") + @Produces("text/html; charset=UTF-8") + public Response emails(@QueryParam("key") final String key, + @QueryParam("signed") final boolean signed, + @QueryParam("emailOnly") final boolean emailOnly, + @Context UriInfo uriInfo) { + Signature2 signature = Signature2.fromBandana(bandanaManager, key); + + if (signature == null) { + log.error("A signature is required to call this method.", new NullPointerException("signature")); + return Response.noContent().build(); + } + + Map profiles = contextHelper.getProfiles(userManager, signed + ? signature.getSignatures().keySet() + : signature.getMissingSignatures()); + + Map context = defaultVelocityContext(); + context.put("signature", signature); + String signatureText = format("%s ( %s )", signature.getTitle(), signature.getHash()); + String rawTemplate = signed ? + i18nResolver.getRawText("com.baloise.confluence.digital-signature.signature.service.message.signedUsersEmails") : + i18nResolver.getRawText("com.baloise.confluence.digital-signature.signature.service.message.unsignedUsersEmails"); + context.put("signedOrNotWithHtml", MessageFormat.format(rawTemplate, "", "", signatureText)); + context.put("withNamesChecked", emailOnly ? "" : "checked"); + context.put("signedChecked", signed ? "checked" : ""); + context.put("toggleWithNamesURL", uriInfo.getRequestUriBuilder().replaceQueryParam("emailOnly", !emailOnly).build()); + context.put("toggleSignedURL", uriInfo.getRequestUriBuilder().replaceQueryParam("signed", !signed).build()); + Function mapping = p -> (emailOnly ? p.getEmail() : contextHelper.mailTo(p)).trim(); + context.put("emails", profiles.values().stream() + .filter(contextHelper::hasEmail) + .map(mapping).collect(toList())); + + context.put("currentDate", new Date()); + context.put("date", new DateTool()); + return Response.ok(getRenderedTemplate("templates/email.vm", context)).build(); + } +} diff --git a/src/main/java/com/baloise/confluence/digitalsignature/sal/DummyProfile.java b/src/main/java/com/baloise/confluence/digitalsignature/sal/DummyProfile.java index ac26213..30533b0 100644 --- a/src/main/java/com/baloise/confluence/digitalsignature/sal/DummyProfile.java +++ b/src/main/java/com/baloise/confluence/digitalsignature/sal/DummyProfile.java @@ -7,45 +7,44 @@ public class DummyProfile implements UserProfile { - private String userKey; - - public DummyProfile(String userKey) { - this.userKey = userKey; - } - - @Override - public UserKey getUserKey() { - return new UserKey(userKey); - } - - @Override - public String getUsername() { - return userKey; - } - - @Override - public String getFullName() { - return userKey; - } - - @Override - public String getEmail() { - return ""; - } - - @Override - public URI getProfilePictureUri(int width, int height) { - return null; - } - - @Override - public URI getProfilePictureUri() { - return null; - } - - @Override - public URI getProfilePageUri() { - return null; - } - + private final String userKey; + + public DummyProfile(String userKey) { + this.userKey = userKey; + } + + @Override + public UserKey getUserKey() { + return new UserKey(userKey); + } + + @Override + public String getUsername() { + return userKey; + } + + @Override + public String getFullName() { + return userKey; + } + + @Override + public String getEmail() { + return ""; + } + + @Override + public URI getProfilePictureUri(int width, int height) { + return null; + } + + @Override + public URI getProfilePictureUri() { + return null; + } + + @Override + public URI getProfilePageUri() { + return null; + } } diff --git a/src/main/resources/META-INF/spring/plugin-context.xml b/src/main/resources/META-INF/spring/plugin-context.xml index 8f0fba7..faba45e 100644 --- a/src/main/resources/META-INF/spring/plugin-context.xml +++ b/src/main/resources/META-INF/spring/plugin-context.xml @@ -1,7 +1,7 @@ - 0) { - let shownSignees = Math.min(signedList.length, Math.ceil(remainingCount / 2)); - remainingCount = remainingCount - shownSignees; - for (let i = 0; i < signedList.length - shownSignees; i++) { - AJS.$(signedList[i]).hide(); - isSomethingHidden = true; - } + const signedList = $ul.find("li.signeelist-signed"); + const missingList = $ul.find("li.signeelist-missing"); + + let remainingCount = limit; + + let isSomethingHidden = false; + if (signedList.length > 0) { + let shownSignees = Math.min(signedList.length, Math.ceil(remainingCount / 2)); + remainingCount = remainingCount - shownSignees; + for (let i = 0; i < signedList.length - shownSignees; i++) { + AJS.$(signedList[i]).hide(); + isSomethingHidden = true; } + } - for (let i = 0; i < missingList.length - remainingCount; i++) { - AJS.$(missingList[i]).hide(); - isSomethingHidden = true; - } + for (let i = 0; i < missingList.length - remainingCount; i++) { + AJS.$(missingList[i]).hide(); + isSomethingHidden = true; + } - return isSomethingHidden; + return isSomethingHidden; } function showAllElements($ul) { - $ul.find("li").show(); - $ul.siblings("a.show-all").remove(); + $ul.find("li").show(); + $ul.siblings("a.show-all").remove(); } function bindCollapse(ul, limit, showMore) { - if (limit < 0) { - return; - } - - let $ul = AJS.$(ul); - - if (hideElements($ul, limit)) { - $ul.after("" + showMore + ""); - $ul.siblings("a.show-all").bind("click", function () { - showAllElements($ul); - }); - } + if (limit < 0) { + return; + } + + let $ul = AJS.$(ul); + + if (hideElements($ul, limit)) { + $ul.after("" + showMore + ""); + $ul.siblings("a.show-all").bind("click", function () { + showAllElements($ul); + }); + } } diff --git a/src/main/resources/templates/export.vm b/src/main/resources/templates/export.vm index dae8695..c20770c 100644 --- a/src/main/resources/templates/export.vm +++ b/src/main/resources/templates/export.vm @@ -1,12 +1,12 @@ #set( $dateFormatter = $action.getDateFormatter())

$signature.getTitle()

#set($bodyWithHtml = $markdown.toHTML($signature.getBody())) @@ -15,18 +15,18 @@ #foreach ($date2userName in $orderedSignatures) #set( $userName = $date2userName.key) #set( $profile = $profiles.get($userName)) - - $dateFormatter.formatDateTime($date2userName.value) - $profile.getFullName() - $profile.getEmail() - + + $dateFormatter.formatDateTime($date2userName.value) + $profile.getFullName() + $profile.getEmail() + #end #foreach( $profile in $orderedMissingSignatureProfiles) - - - $profile.getFullName() - $profile.getEmail() - + + + $profile.getFullName() + $profile.getEmail() + #end diff --git a/src/main/resources/templates/macro.vm b/src/main/resources/templates/macro.vm index be6ef39..77f2705 100644 --- a/src/main/resources/templates/macro.vm +++ b/src/main/resources/templates/macro.vm @@ -5,81 +5,81 @@ #set($macroId = $signature.getKey().replace("signature.", "")) #if($panel)
-
$title   - +
#else - $title + $title #end #set($bodyWithHtml = $markdown.toHTML($signature.getBody())) -

$bodyWithHtml

-
    - #foreach ($date2userName in $orderedSignatures) - #set( $userName = $date2userName.key) - #set( $profile = $profiles.get($userName)) -
  • - $dateFormatter.formatDateTime($date2userName.value) - $profile.getFullName() -
  • - #end - #foreach( $profile in $orderedMissingSignatureProfiles) -
  • - $profile.getFullName() -
  • - #end -
- #if($signAs) -
-
- -
-
- #end +

$bodyWithHtml

+
    + #foreach ($date2userName in $orderedSignatures) + #set( $userName = $date2userName.key) + #set( $profile = $profiles.get($userName)) +
  • + $dateFormatter.formatDateTime($date2userName.value) - $profile.getFullName() +
  • + #end + #foreach( $profile in $orderedMissingSignatureProfiles) +
  • + $profile.getFullName() +
  • + #end +
+#if($signAs) +
+
+ +
+
+#end #if($panel)
#end diff --git a/src/test/java/com/baloise/confluence/digitalsignature/DigitalSignatureMacroTest.java b/src/test/java/com/baloise/confluence/digitalsignature/DigitalSignatureMacroTest.java index 44bb605..8a0aa02 100644 --- a/src/test/java/com/baloise/confluence/digitalsignature/DigitalSignatureMacroTest.java +++ b/src/test/java/com/baloise/confluence/digitalsignature/DigitalSignatureMacroTest.java @@ -1,25 +1,25 @@ package com.baloise.confluence.digitalsignature; -import java.util.ArrayList; -import java.util.List; - -import org.junit.Test; - +import com.atlassian.bandana.BandanaManager; import com.atlassian.confluence.setup.BootstrapManager; import com.atlassian.sal.api.user.UserProfile; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -public class DigitalSignatureMacroTest { - - private final Signature signature = new Signature(1, "test", "title"); +class DigitalSignatureMacroTest { + private final Signature2 signature = new Signature2(1, "test", "title"); private final BootstrapManager bootstrapManager = mock(BootstrapManager.class); + private final BandanaManager bandana = mock(BandanaManager.class); @Test - public void getMailtoLong() { - DigitalSignatureMacro macro = new DigitalSignatureMacro(null, null, null, null, null, null, null); + void getMailtoLong() { + DigitalSignatureMacro macro = new DigitalSignatureMacro(bandana, null, null, null, null, null, null); List profiles = new ArrayList<>(); UserProfile profile = mock(UserProfile.class); when(profile.getFullName()).thenReturn("Heinz Meier"); @@ -27,15 +27,17 @@ public void getMailtoLong() { for (int i = 0; i < 20; i++) { profiles.add(profile); } + String mailto = macro.getMailto(profiles, "Subject", true, null); + assertEquals("mailto:heinz.meier@meier.com,heinz.meier@meier.com,heinz.meier@meier.com,heinz.meier@meier.com,heinz.meier@meier.com,heinz.meier@meier.com,heinz.meier@meier.com,heinz.meier@meier.com,heinz.meier@meier.com,heinz.meier@meier.com,heinz.meier@meier.com,heinz.meier@meier.com,heinz.meier@meier.com,heinz.meier@meier.com,heinz.meier@meier.com,heinz.meier@meier.com,heinz.meier@meier.com,heinz.meier@meier.com,heinz.meier@meier.com,heinz.meier@meier.com?Subject=Subject", mailto); } @Test - public void getMailtoVeryLong() { + void getMailtoVeryLong() { when(bootstrapManager.getWebAppContextPath()).thenReturn("nirvana"); - DigitalSignatureMacro macro = new DigitalSignatureMacro(null, null, bootstrapManager, null, null, null, null); + DigitalSignatureMacro macro = new DigitalSignatureMacro(bandana, null, bootstrapManager, null, null, null, null); List profiles = new ArrayList<>(); UserProfile profile = mock(UserProfile.class); when(profile.getFullName()).thenReturn("Heinz Meier"); @@ -43,19 +45,23 @@ public void getMailtoVeryLong() { for (int i = 0; i < 200; i++) { profiles.add(profile); } + String mailto = macro.getMailto(profiles, "Subject", true, signature); + assertEquals("nirvana/rest/signature/1.0/emails?key=signature.3224a4d6bba68cd0ece9b64252f8bf5677e24cf6b7c5f543e3176d419d34d517&signed=true", mailto); } @Test - public void getMailtoShort() { - DigitalSignatureMacro macro = new DigitalSignatureMacro(null, null, null, null, null, null, null); + void getMailtoShort() { + DigitalSignatureMacro macro = new DigitalSignatureMacro(bandana, null, null, null, null, null, null); List profiles = new ArrayList<>(); UserProfile profile = mock(UserProfile.class); when(profile.getFullName()).thenReturn("Heinz Meier"); when(profile.getEmail()).thenReturn("heinz.meier@meier.com"); profiles.add(profile); + String mailto = macro.getMailto(profiles, "Subject", true, null); + assertEquals("mailto:Heinz Meier?Subject=Subject", mailto); } } diff --git a/src/test/java/com/baloise/confluence/digitalsignature/InheritSignersTest.java b/src/test/java/com/baloise/confluence/digitalsignature/InheritSignersTest.java index 3d60ce5..9d124bc 100644 --- a/src/test/java/com/baloise/confluence/digitalsignature/InheritSignersTest.java +++ b/src/test/java/com/baloise/confluence/digitalsignature/InheritSignersTest.java @@ -1,25 +1,24 @@ package com.baloise.confluence.digitalsignature; -import org.junit.Test; -import static com.baloise.confluence.digitalsignature.InheritSigners.NONE; -import static com.baloise.confluence.digitalsignature.InheritSigners.READERS_ONLY; -import static com.baloise.confluence.digitalsignature.InheritSigners.ofValue; -import static org.junit.Assert.assertEquals; +import org.junit.jupiter.api.Test; -public class InheritSignersTest { +import static com.baloise.confluence.digitalsignature.InheritSigners.*; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class InheritSignersTest { @Test - public void testOfValueReadersOnly() { + void testOfValueReadersOnly() { assertEquals(READERS_ONLY, ofValue("readers only")); } @Test - public void testOfValueNoneNull() { + void testOfValueNoneNull() { assertEquals(NONE, ofValue(null)); } @Test - public void testOfValueNoneIllegalArgument() { + void testOfValueNoneIllegalArgument() { assertEquals(NONE, ofValue("asdasd")); } } diff --git a/src/test/java/com/baloise/confluence/digitalsignature/MarkdownTest.java b/src/test/java/com/baloise/confluence/digitalsignature/MarkdownTest.java index e87b7a6..575b2e8 100644 --- a/src/test/java/com/baloise/confluence/digitalsignature/MarkdownTest.java +++ b/src/test/java/com/baloise/confluence/digitalsignature/MarkdownTest.java @@ -1,33 +1,36 @@ package com.baloise.confluence.digitalsignature; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.net.URISyntaxException; import static java.nio.file.Files.readAllLines; import static java.nio.file.Paths.get; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; -public class MarkdownTest { - private Markdown markdown; +class MarkdownTest { + private Markdown markdown; - @Before - public void setUp() { - markdown = new Markdown(); - } + @BeforeEach + void setUp() { + markdown = new Markdown(); + } - @Test - public void testToHTML() throws Exception { - assertEquals("

This is Sparta

\n", markdown.toHTML("This is *Sparta*")); - assertEquals("

Link

\n", markdown.toHTML("[Link](http://a.com)")); - assertEquals("

\n", markdown.toHTML("![Image](http://url/a.png)")); - assertEquals("

<b></b>

\n", markdown.toHTML("")); - assertEquals(readResource("commonmark.html").trim(), markdown.toHTML(readResource("commonmark.md")).trim()); - } + @Test + void testToHTML() { + assertAll( + () -> assertEquals("

This is Sparta

\n", markdown.toHTML("This is *Sparta*")), + () -> assertEquals("

Link

\n", markdown.toHTML("[Link](http://a.com)")), + () -> assertEquals("

\n", markdown.toHTML("![Image](http://url/a.png)")), + () -> assertEquals("

<b></b>

\n", markdown.toHTML("")), + () -> assertEquals(readResource("commonmark.html").trim(), markdown.toHTML(readResource("commonmark.md")).trim()) + ); + } - private String readResource(String name) throws IOException, URISyntaxException { - return String.join("\n", readAllLines(get(getClass().getResource("/" + name).toURI()))); - } + private String readResource(String name) throws IOException, URISyntaxException { + return String.join("\n", readAllLines(get(getClass().getResource("/" + name).toURI()))); + } } diff --git a/src/test/java/com/baloise/confluence/digitalsignature/MessageFormatTest.java b/src/test/java/com/baloise/confluence/digitalsignature/MessageFormatTest.java index 4d2dde1..5470115 100644 --- a/src/test/java/com/baloise/confluence/digitalsignature/MessageFormatTest.java +++ b/src/test/java/com/baloise/confluence/digitalsignature/MessageFormatTest.java @@ -1,19 +1,23 @@ package com.baloise.confluence.digitalsignature; -import java.text.MessageFormat; +import org.junit.jupiter.api.Test; -import org.junit.Test; +import java.text.MessageFormat; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; -public class MessageFormatTest { +class MessageFormatTest { @Test - public void test() { + void testFormat_inOrder() { String rawTemplate = "Email addresses of users who {0}signed{1} {2}"; String actual = MessageFormat.format(rawTemplate, "", "", "#123"); assertEquals("Email addresses of users who signed #123", actual); - rawTemplate = "{2} was {0}signed{1}"; - actual = MessageFormat.format(rawTemplate, "", "", "#123"); + } + + @Test + void testFormat_outOfOrder() { + String rawTemplate = "{2} was {0}signed{1}"; + String actual = MessageFormat.format(rawTemplate, "", "", "#123"); assertEquals("#123 was signed", actual); } } diff --git a/src/test/java/com/baloise/confluence/digitalsignature/SignatureSerialisationTest.java b/src/test/java/com/baloise/confluence/digitalsignature/SignatureSerialisationTest.java index 165ce7f..8fe1be9 100644 --- a/src/test/java/com/baloise/confluence/digitalsignature/SignatureSerialisationTest.java +++ b/src/test/java/com/baloise/confluence/digitalsignature/SignatureSerialisationTest.java @@ -1,41 +1,87 @@ package com.baloise.confluence.digitalsignature; -import org.junit.Test; +import com.atlassian.bandana.BandanaManager; +import org.junit.jupiter.api.Test; -import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Date; +import java.util.HashSet; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -public class SignatureSerialisationTest { - @Test - public void deserialise() throws IOException, ClassNotFoundException { - ObjectInputStream in = new ObjectInputStream(getClass().getResourceAsStream("/signature.ser")); - Signature signature = (Signature) in.readObject(); - in.close(); - assertEquals("signature.a077cdcc5bfcf275fe447ae2c609c1c361331b4e90cb85909582e0d824cbc5b3", signature.getKey()); - assertEquals("[missing1, missing2]", signature.getMissingSignatures().toString()); - assertEquals(1, signature.getSignatures().size()); - assertTrue(signature.getSignatures().containsKey("signed1")); - assertEquals(9999, signature.getSignatures().get("signed1").getTime()); +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class SignatureSerialisationTest { + public static final String SIG_JSON = "{\"key\":\"signature.a077cdcc5bfcf275fe447ae2c609c1c361331b4e90cb85909582e0d824cbc5b3\",\"hash\":\"a077cdcc5bfcf275fe447ae2c609c1c361331b4e90cb85909582e0d824cbc5b3\",\"pageId\":123,\"title\":\"title\",\"body\":\"body\",\"maxSignatures\":-1,\"visibilityLimit\":-1,\"signatures\":{\"signed1\":\"1970-01-01T01:00:09CET\"},\"missingSignatures\":[\"missing1\",\"missing2\"],\"notify\":[\"notify1\"]}"; + + @Test + void deserialize() throws IOException, ClassNotFoundException { + String signatureKey = "signature.a077cdcc5bfcf275fe447ae2c609c1c361331b4e90cb85909582e0d824cbc5b3"; + + Signature2 signature; + try(ObjectInputStream in = new ObjectInputStream(getClass().getResourceAsStream("/signature.ser"))) { + + HashSet keys = new HashSet<>(); + keys.add(signatureKey); + BandanaManager mgr = mock(BandanaManager.class); + when(mgr.getValue(any(), any())).thenReturn(in.readObject()); + when(mgr.getKeys(any())).thenReturn(keys); + + signature = Signature2.fromBandana(mgr, signatureKey); } - @Test - public void serialise() throws IOException, ClassNotFoundException { - Signature signature = new Signature(123L, "body", "title"); - signature.getNotify().add("notify1"); - signature.getMissingSignatures().add("missing1"); - signature.getMissingSignatures().add("missing2"); - signature.getSignatures().put("signed1", new Date(9999)); - ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("src/test/resources/signature-test.ser")); - out.writeObject(signature); - out.close(); - - ObjectInputStream in = new ObjectInputStream(this.getClass().getResourceAsStream("/signature.ser")); - assertEquals(signature, in.readObject()); + assertNotNull(signature); + assertAll( + () -> assertEquals(signatureKey, signature.getKey()), + () -> assertEquals("[missing1, missing2]", signature.getMissingSignatures().toString()), + () -> assertEquals(1, signature.getSignatures().size()), + () -> assertTrue(signature.getSignatures().containsKey("signed1")), + () -> assertEquals(9999, signature.getSignatures().get("signed1").getTime()), + + // assert we can still read the old gson serialization + () -> assertEquals(signature, Signature2.deserialize(SIG_JSON)), + + // assert that deserialization of the serialization results in the original Signature + () -> assertEquals(signature, Signature2.deserialize(signature.serialize())) + ); + } + + @Test + void serialize() throws IOException, ClassNotFoundException { + Signature2 signature = new Signature2(123L, "body", "title"); + signature.getNotify().add("notify1"); + signature.getMissingSignatures().add("missing1"); + signature.getMissingSignatures().add("missing2"); + signature.getSignatures().put("signed1", new Date(9999)); + + Path path = Paths.get("src/test/resources/signature-test.ser"); + try(ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(path))) { + out.writeObject(signature); } + + // assert the serialization we just wrote can be deserialized + ObjectInputStream in = new ObjectInputStream(Files.newInputStream(path)); + assertEquals(signature, in.readObject()); + } + + @Test + void deserializeHistoricalRecord() throws IOException, ClassNotFoundException { + Signature signature = new Signature(123L, "body", "title"); + signature.getNotify().add("notify1"); + signature.getMissingSignatures().add("missing1"); + signature.getMissingSignatures().add("missing2"); + signature.getSignatures().put("signed1", new Date(9999)); + + ObjectInputStream in; + + // assert the historically serialized class can still be deserialized + in = new ObjectInputStream(this.getClass().getResourceAsStream("/signature.ser")); + assertEquals(signature, in.readObject()); + } } diff --git a/src/test/java/com/baloise/confluence/digitalsignature/SignatureTest.java b/src/test/java/com/baloise/confluence/digitalsignature/SignatureTest.java index fe281d5..84023b7 100644 --- a/src/test/java/com/baloise/confluence/digitalsignature/SignatureTest.java +++ b/src/test/java/com/baloise/confluence/digitalsignature/SignatureTest.java @@ -1,19 +1,112 @@ package com.baloise.confluence.digitalsignature; -import org.junit.Test; +import com.atlassian.bandana.BandanaManager; +import com.atlassian.bandana.DefaultBandanaManager; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; +import java.util.Collections; -public class SignatureTest { +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.*; - @Test - public void testClone() throws Exception { - Signature signature = new Signature(999, "title", "body"); - signature.getMissingSignatures().add("Hans"); - Signature cloned = signature.clone(); - assertFalse(signature == cloned); - assertEquals(signature, cloned); - assertEquals("Hans", cloned.getMissingSignatures().iterator().next()); +class SignatureTest { + @Nested + class SerializationTest { + @Test + void serialize_empty() { + Signature2 signature = new Signature2(); + + String json = signature.serialize(); + + assertEquals("{\"key\":\"\",\"hash\":\"\",\"pageId\":0,\"title\":\"\",\"body\":\"\",\"maxSignatures\":-1,\"visibilityLimit\":-1,\"signatures\":{},\"missingSignatures\":[],\"notify\":[]}", json); + } + + @Test + void serialize_initializedObject() { + Signature2 signature = new Signature2(42L, "body text", "title text"); + signature.sign("max.mustermann"); + signature.setMissingSignatures(Collections.singleton("max.muster")); + signature.setNotify(Collections.singleton("max.meier")); + + String json = signature.serialize(); + + assertEquals("{\"key\":\"signature.752b4cc6b4933fc7f0a6efa819c1bcc440c32155457e836d99d1bfe927cc22f5\",\"hash\":\"752b4cc6b4933fc7f0a6efa819c1bcc440c32155457e836d99d1bfe927cc22f5\",\"pageId\":42,\"title\":\"title text\",\"body\":\"body text\",\"maxSignatures\":-1,\"visibilityLimit\":-1,\"signatures\":{},\"missingSignatures\":[\"max.muster\"],\"notify\":[\"max.meier\"]}", json); + } + + @Test + void deserialize_empty() { + assertNull(Signature2.deserialize(null)); + assertNull(Signature2.deserialize("")); + } + + @Test + void serializeAndDeserialize() { + Signature2 signature = new Signature2(42L, "body text", "title text"); + signature.sign("max.mustermann"); + signature.setMissingSignatures(Collections.singleton("max.muster")); + signature.setNotify(Collections.singleton("max.meier")); + + String json = signature.serialize(); + + Signature2 restoredSignature = Signature2.deserialize(json); + + assertEquals("{\"key\":\"signature.752b4cc6b4933fc7f0a6efa819c1bcc440c32155457e836d99d1bfe927cc22f5\",\"hash\":\"752b4cc6b4933fc7f0a6efa819c1bcc440c32155457e836d99d1bfe927cc22f5\",\"pageId\":42,\"title\":\"title text\",\"body\":\"body text\",\"maxSignatures\":-1,\"visibilityLimit\":-1,\"signatures\":{},\"missingSignatures\":[\"max.muster\"],\"notify\":[\"max.meier\"]}", json); + assertEquals(signature, restoredSignature); + } + } + + @Nested + class BandanaWrapperTest { + private final BandanaManager bandana = mock(DefaultBandanaManager.class); + private final Signature2 signature = new Signature2(1, "test", "title"); + private final Signature signatureOld = new Signature(1, "test", "title"); + + @Test + void toBandanaFromBandana_readAsWritten() { + ArgumentCaptor stringCapator = ArgumentCaptor.forClass(String.class); + ArgumentCaptor objectCapator = ArgumentCaptor.forClass(Object.class); + + String key = signature.getKey(); + assertNull(Signature2.fromBandana(bandana, key), "Should not be there yet."); + + doNothing().when(bandana).setValue(any(), stringCapator.capture(), objectCapator.capture()); + when(bandana.getKeys(any())).thenReturn(Collections.singletonList(key)); + + Signature2.toBandana(bandana, signature); + assertEquals(key, stringCapator.getValue()); + assertEquals(signature.serialize(), objectCapator.getValue()); + + when(bandana.getValue(any(), any())).thenCallRealMethod(); + when(bandana.getValue(any(), eq(key), eq(true))).thenReturn(signature.serialize()); + assertEquals(signature, Signature2.fromBandana(bandana, signature.getKey())); + } + + @Test + void fromBandana_signature_signature() { + String key = signature.getKey(); + assertNull(Signature2.fromBandana(bandana, key), "Should not be there yet."); + + when(bandana.getKeys(any())).thenReturn(Collections.singletonList(key)); + when(bandana.getValue(any(), any())).thenCallRealMethod(); + when(bandana.getValue(any(), eq(key), eq(true))).thenReturn(signatureOld); + + assertEquals(signature, Signature2.fromBandana(bandana, signature.getKey())); + } + + @Test + void fromBandana_string_signature() { + String key = signature.getKey(); + assertNull(Signature2.fromBandana(bandana, key), "Should not be there yet."); + + when(bandana.getKeys(any())).thenReturn(Collections.singletonList(key)); + when(bandana.getValue(any(), any())).thenCallRealMethod(); + when(bandana.getValue(any(), eq(key), eq(true))).thenReturn(signature.serialize()); + + assertEquals(signature, Signature2.fromBandana(bandana, signature.getKey())); + } } } diff --git a/src/test/java/com/baloise/confluence/digitalsignature/TemplatesTest.java b/src/test/java/com/baloise/confluence/digitalsignature/TemplatesTest.java index e4930a3..d2112e3 100644 --- a/src/test/java/com/baloise/confluence/digitalsignature/TemplatesTest.java +++ b/src/test/java/com/baloise/confluence/digitalsignature/TemplatesTest.java @@ -1,46 +1,46 @@ package com.baloise.confluence.digitalsignature; import org.apache.velocity.VelocityContext; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.io.BufferedWriter; import java.io.StringWriter; import java.io.Writer; import static org.apache.velocity.app.Velocity.mergeTemplate; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; -public class TemplatesTest { - private static String normalize(String input) { - return input.replaceAll("[\n\r]", "") - .replaceAll(" +", " ") - .replaceAll("> <", "><") - .trim(); - } +class TemplatesTest { + private static String normalize(String input) { + return input.replaceAll("[\n\r]", "") + .replaceAll(" +", " ") + .replaceAll("> <", "><") + .trim(); + } - @Test - public void testMacroVm() throws Exception { - StringWriter sw = new StringWriter(); - //lets use BufferedWriter for better performance: - Writer writer = new BufferedWriter(sw); - VelocityContext context = new VelocityContext(); - //add your parameters to context - mergeTemplate("src/main/resources/templates/macro.vm", "UTF-8", context, writer); - writer.flush(); - String expected = "#requireResource(\"com.baloise.confluence.digital-signature:digital-signature-resources\") $title

$bodyWithHtml

    "; - assertEquals(expected, normalize(sw.toString())); - } + @Test + void testMacroVm() throws Exception { + StringWriter sw = new StringWriter(); + //lets use BufferedWriter for better performance: + Writer writer = new BufferedWriter(sw); + VelocityContext context = new VelocityContext(); + //add your parameters to context + mergeTemplate("src/main/resources/templates/macro.vm", "UTF-8", context, writer); + writer.flush(); + String expected = "#requireResource(\"com.baloise.confluence.digital-signature:digital-signature-resources\") $title

    $bodyWithHtml

      "; + assertEquals(expected, normalize(sw.toString())); + } - @Test - public void testExportVm() throws Exception { - StringWriter sw = new StringWriter(); - //lets use BufferedWriter for better performance: - Writer writer = new BufferedWriter(sw); - VelocityContext context = new VelocityContext(); - //add your parameters to context - mergeTemplate("src/main/resources/templates/export.vm", "UTF-8", context, writer); - writer.flush(); - String expected = "

      $signature.getTitle()

      $bodyWithHtml

      "; - assertEquals(expected, normalize(sw.toString())); - } + @Test + void testExportVm() throws Exception { + StringWriter sw = new StringWriter(); + //lets use BufferedWriter for better performance: + Writer writer = new BufferedWriter(sw); + VelocityContext context = new VelocityContext(); + //add your parameters to context + mergeTemplate("src/main/resources/templates/export.vm", "UTF-8", context, writer); + writer.flush(); + String expected = "

      $signature.getTitle()

      $bodyWithHtml

      "; + assertEquals(expected, normalize(sw.toString())); + } } diff --git a/src/test/java/com/baloise/confluence/digitalsignature/UserProfileByNameTest.java b/src/test/java/com/baloise/confluence/digitalsignature/UserProfileByNameTest.java index 5c731c8..226d99f 100644 --- a/src/test/java/com/baloise/confluence/digitalsignature/UserProfileByNameTest.java +++ b/src/test/java/com/baloise/confluence/digitalsignature/UserProfileByNameTest.java @@ -1,31 +1,31 @@ package com.baloise.confluence.digitalsignature; import com.atlassian.sal.api.user.UserProfile; -import org.junit.Test; -import org.mockito.Mockito; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; import java.util.SortedSet; import java.util.TreeSet; -import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -public class UserProfileByNameTest { +class UserProfileByNameTest { + @Test + void testCompare() { + UserProfile profile1 = mock(UserProfile.class); + when(profile1.getFullName()).thenReturn("Heinz Meier"); + when(profile1.getEmail()).thenReturn("heinz.meier@meier.com"); + when(profile1.toString()).thenReturn("Heinz Meier"); + UserProfile profile2 = mock(UserProfile.class); + when(profile2.getFullName()).thenReturn("Abraham Aebischer"); + when(profile2.getEmail()).thenReturn("Abraham Aebischer@meier.com"); + when(profile2.toString()).thenReturn("Abraham Aebischer"); + SortedSet profiles = new TreeSet<>(new UserProfileByName()); + profiles.add(profile1); + profiles.add(profile2); + profiles.add(profile1); - @Test - public void testCompare() { - UserProfile profile1 = Mockito.mock(UserProfile.class); - when(profile1.getFullName()).thenReturn("Heinz Meier"); - when(profile1.getEmail()).thenReturn("heinz.meier@meier.com"); - when(profile1.toString()).thenReturn("Heinz Meier"); - UserProfile profile2 = Mockito.mock(UserProfile.class); - when(profile2.getFullName()).thenReturn("Abraham Aebischer"); - when(profile2.getEmail()).thenReturn("Abraham Aebischer@meier.com"); - when(profile2.toString()).thenReturn("Abraham Aebischer"); - SortedSet profiles = new TreeSet<>(new UserProfileByName()); - profiles.add(profile1); - profiles.add(profile2); - profiles.add(profile1); - assertEquals("[Abraham Aebischer, Heinz Meier]", profiles.toString()); - } + Assertions.assertEquals("[Abraham Aebischer, Heinz Meier]", profiles.toString()); + } } diff --git a/src/test/java/ut/com/baloise/confluence/digitalsignature/MyComponentUnitTest.java b/src/test/java/ut/com/baloise/confluence/digitalsignature/MyComponentUnitTest.java index b3e44db..6e66e1c 100644 --- a/src/test/java/ut/com/baloise/confluence/digitalsignature/MyComponentUnitTest.java +++ b/src/test/java/ut/com/baloise/confluence/digitalsignature/MyComponentUnitTest.java @@ -1,15 +1,14 @@ package ut.com.baloise.confluence.digitalsignature; -import org.junit.Test; import com.baloise.confluence.digitalsignature.api.DigitalSignatureComponent; import com.baloise.confluence.digitalsignature.impl.DigitalSignatureComponentImpl; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertEquals; - -public class MyComponentUnitTest { - @Test - public void testMyName() { - DigitalSignatureComponent component = new DigitalSignatureComponentImpl(null); - assertEquals("names do not match!", "digitalSignatureComponent", component.getName()); - } +class MyComponentUnitTest { + @Test + void testMyName() { + DigitalSignatureComponent component = new DigitalSignatureComponentImpl(null); + Assertions.assertEquals("digitalSignatureComponent", component.getName(), "names do not match!"); + } } diff --git a/src/test/resources/atlassian-plugin.xml b/src/test/resources/atlassian-plugin.xml index ccf2a09..140bc08 100644 --- a/src/test/resources/atlassian-plugin.xml +++ b/src/test/resources/atlassian-plugin.xml @@ -2,13 +2,13 @@ ${project.description} ${project.version} - + - + - - - \ No newline at end of file + +