diff --git a/.gitignore b/.gitignore index 0d85da0..6c03aa4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,9 @@ +/src/test/resources/signature-test.ser + /.settings /.classpath /.project /target .idea -*.iml \ No newline at end of file +*.iml diff --git a/README.RELEASE.MD b/README.RELEASE.MD index 5fed2b6..deb768c 100644 --- a/README.RELEASE.MD +++ b/README.RELEASE.MD @@ -1,7 +1,7 @@ -- check fixed issues are assigned to milestone : https://github.com/baloise/digital-signature/milestones +- check fixed issues are assigned to milestone: https://github.com/baloise/digital-signature/milestones - increase version in POM -- mvn clean package +- `mvn clean package` - merge onto release branch - set tag - push and wait for https://travis-ci.org/baloise/digital-signature/branches to succeed -- create a version in marketplace https://marketplace.atlassian.com/manage/plugins/com.baloise.confluence.digital-signature/versions and upload obr ( from https://github.com/baloise/digital-signature/tree/gh-pages/release ) +- create a version in marketplace https://marketplace.atlassian.com/manage/plugins/com.baloise.confluence.digital-signature/versions and upload obr (from https://github.com/baloise/digital-signature/tree/gh-pages/release) diff --git a/development.txt b/development.md similarity index 68% rename from development.txt rename to development.md index c475c1f..3820c4b 100644 --- a/development.txt +++ b/development.md @@ -1,32 +1,42 @@ -Install +# Install https://www.atlassian.com/software/confluence/download-archives -Apache Commons FileUpload Bundle +- Apache Commons FileUpload Bundle http://central.maven.org/maven2/commons-fileupload/commons-fileupload/1.3/commons-fileupload-1.3.jar -Atlassian PDK Install Plugin +- 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 +## License https://my.atlassian.com/products/index -Run +# 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, starup is slow on my box) +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/ +As smtp server use `https://mailtrap.io/` diff --git a/docs/privacy-policy.md b/docs/privacy-policy.md index f25afc5..7f6484a 100644 --- a/docs/privacy-policy.md +++ b/docs/privacy-policy.md @@ -1,5 +1,3 @@ -We do not transfer or store any data outside your Atlassian product. - -We have no access to any data your stored within your Atlassian product. - -You data is yours - no strings attached. +- 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. +- Your data is yours - no strings attached. diff --git a/icon/CREDITS b/icon/CREDITS index 1baf4bf..1fdaced 100644 --- a/icon/CREDITS +++ b/icon/CREDITS @@ -1 +1 @@ -Icon made by http://www.flaticon.com/authors/freepik \ No newline at end of file +Icon made by http://www.flaticon.com/authors/freepik diff --git a/pom.xml b/pom.xml index 3e85885..f50f4ca 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ 4.0.0 com.baloise.confluence digital-signature - 7.0.1 + 7.0.2 Baloise http://www.baloise.ch/ @@ -37,7 +37,7 @@ junit junit - 4.13 + 4.13.1 test @@ -111,6 +111,15 @@ + com.atlassian.maven.plugins confluence-maven-plugin @@ -120,6 +129,7 @@ ${confluence.version} ${confluence.data.version} false + false ${atlassian.plugin.key} diff --git a/src/main/java/com/baloise/confluence/digitalsignature/ContextHelper.java b/src/main/java/com/baloise/confluence/digitalsignature/ContextHelper.java index 448c56d..e610b22 100644 --- a/src/main/java/com/baloise/confluence/digitalsignature/ContextHelper.java +++ b/src/main/java/com/baloise/confluence/digitalsignature/ContextHelper.java @@ -1,76 +1,69 @@ package com.baloise.confluence.digitalsignature; -import static java.lang.String.format; - -import java.util.Comparator; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.SortedSet; -import java.util.TreeSet; - import com.atlassian.sal.api.user.UserManager; import com.atlassian.sal.api.user.UserProfile; import com.baloise.confluence.digitalsignature.sal.DummyProfile; +import java.util.*; +import java.util.Map.Entry; +import java.util.function.Function; + +import static java.lang.String.format; + public class ContextHelper { - public Object getOrderedSignatures(Signature signature) { - Comparator> comparator = new Comparator>() { - @Override - public int compare(Entry s1, Entry s2) { - int ret = s1.getValue().compareTo(s2.getValue()); - return ret == 0 ? s1.getKey().compareTo(s2.getKey()) : ret; - } - }; - SortedSet> ret = new TreeSet>(comparator); - ret.addAll(signature.getSignatures().entrySet()); - return ret; - } - public Map union(Map ...maps) { - Map union = new HashMap(); - for (Map map : maps) { - union.putAll(map); - } - return union; - } - - public 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 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 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 Object getOrderedSignatures(Signature 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 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 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 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(); + } } diff --git a/src/main/java/com/baloise/confluence/digitalsignature/DigitalSignatureMacro.java b/src/main/java/com/baloise/confluence/digitalsignature/DigitalSignatureMacro.java index 39220ba..953ee4a 100644 --- a/src/main/java/com/baloise/confluence/digitalsignature/DigitalSignatureMacro.java +++ b/src/main/java/com/baloise/confluence/digitalsignature/DigitalSignatureMacro.java @@ -1,35 +1,10 @@ package com.baloise.confluence.digitalsignature; -import static com.atlassian.confluence.renderer.radeox.macros.MacroUtils.defaultVelocityContext; -import static com.atlassian.confluence.security.ContentPermission.EDIT_PERMISSION; -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 java.util.Arrays.asList; -import static java.util.Collections.emptyMap; -import static java.util.Collections.emptySet; -import static java.util.stream.Collectors.toList; - -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.util.Collection; -import java.util.HashSet; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.TreeSet; -import java.util.UUID; - -import org.apache.velocity.tools.generic.DateTool; -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; import com.atlassian.confluence.core.DefaultSaveContext; import com.atlassian.confluence.macro.Macro; -import com.atlassian.confluence.macro.MacroExecutionException; import com.atlassian.confluence.pages.Page; import com.atlassian.confluence.pages.PageManager; import com.atlassian.confluence.security.ContentPermission; @@ -48,333 +23,355 @@ 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.security.InvalidParameterException; +import java.util.*; + +import static com.atlassian.confluence.renderer.radeox.macros.MacroUtils.defaultVelocityContext; +import static com.atlassian.confluence.security.ContentPermission.EDIT_PERMISSION; +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 java.util.Arrays.asList; +import static java.util.Collections.emptyMap; +import static java.util.Collections.emptySet; +import static java.util.stream.Collectors.toList; @Scanned public class DigitalSignatureMacro implements Macro { - private BandanaManager bandanaManager; - private UserManager userManager; - private BootstrapManager bootstrapManager; - private PageManager pageManager; - private final String REST_PATH = "/rest/signature/1.0"; - private final String DISPLAY_PATH = "/display"; - private ContextHelper contextHelper = new ContextHelper(); - private final transient Markdown markdown = new Markdown(); - private final PermissionManager permissionManager; - private GroupManager groupManager; - private final Set all = new HashSet(); - final int MAX_MAILTO_CHARACTER_COUNT = 500; - 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("*"); - } - - @Override - public String execute(Map params, String body, ConversionContext conversionContext) throws MacroExecutionException { - if(body != null && body.length() > 10) { - 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)), - signers - ); - ConfluenceUser currentUser = AuthenticatedUserThreadLocal.get(); - String currentUserName = currentUser.getName(); - - boolean protectedContent = getBoolean(params, "protectedContent", false); - boolean protectedContentAccess = protectedContent && (permissionManager.hasPermission(currentUser, Permission.EDIT, entity) ||signature.hasSigned(currentUserName)); - - if(protectedContent && isPage(conversionContext)) { - Page protectedPage = pageManager.getPage(conversionContext.getSpaceKey(), signature.getProtectedKey()); - if(protectedPage == null) { - Page page = (Page) entity; - ContentPermissionSet editors = page.getContentPermissionSet(EDIT_PERMISSION); - if(editors == null || editors.size() == 0) { - return warning(i18nResolver.getText("com.baloise.confluence.digital-signature.signature.macro.warning.editPermissionRequiredForProtectedContent" ,"")); - } - 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); - } - } - - Map context = defaultVelocityContext(); - context.put("date", new DateTool()); - context.put("markdown", markdown); - - 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)) { - Page page = (Page) entity; - context.put("protectedContentURL", bootstrapManager.getWebAppContextPath()+ DISPLAY_PATH+"/"+page.getSpaceKey()+"/"+signature.getProtectedKey()); - } - - 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("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 getRenderedTemplate("templates/macro.vm", context); - } - return warning(i18nResolver.getText("com.baloise.confluence.digital-signature.signature.macro.warning.bodyToShort")); - - - } - - private boolean hideSignatures(Map params, Signature signature, String currentUserName) { - boolean pendingVisible = true; - boolean signaturesVisible = true; - switch (SignaturesVisible.ofValue(params.get("pendingVisible"))) { - case IF_SIGNATORY: - if(!signature.hasSigned(currentUserName) && !signature.isSignatory(currentUserName)) { - pendingVisible = false; - } - break; - case IF_SIGNED: - if(!signature.hasSigned(currentUserName)) { - pendingVisible = false; - } - break; - case ALWAYS: - break; - } - switch (SignaturesVisible.ofValue(params.get("signaturesVisible"))) { - case IF_SIGNATORY: - if(!signature.hasSigned(currentUserName) && !signature.isSignatory(currentUserName)) { - signaturesVisible = false; - } - break; - case IF_SIGNED: - if(!signature.hasSigned(currentUserName)) { - signaturesVisible = false; - } - break; - case ALWAYS: - break; - } - if(!pendingVisible) signature.setMissingSignatures(emptySet()); - if(!signaturesVisible) signature.setSignatures(emptyMap()); - return pendingVisible && signaturesVisible; - } - - 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, ContentPermission.VIEW_PERMISSION)); - users.addAll(loadUsers(conversionContext, ContentPermission.EDIT_PERMISSION)); - break; - case READERS_ONLY: - users.addAll(loadUsers(conversionContext, ContentPermission.VIEW_PERMISSION)); - users.removeAll(loadUsers(conversionContext, ContentPermission.EDIT_PERMISSION)); - break; - case WRITERS_ONLY: - users.addAll(loadUsers(conversionContext, ContentPermission.EDIT_PERMISSION)); - break; - case NONE: - break; - } - 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(); - } - 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.valueOf(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 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(save) save(loaded); - } else { - signature.setMissingSignatures(signers); - save(signature); - } - return 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; - } - - String getMailto(Collection profiles, String subject, boolean signed, Signature signature) { - if(profiles == null || profiles.isEmpty()) return null; - profiles = profiles.stream() - .filter(contextHelper::hasEmail) - .collect(toList()); - StringBuilder ret = new StringBuilder("mailto:"); - for (UserProfile profile : profiles) { - if(ret.length()>7) ret.append(','); - ret.append(contextHelper.mailTo(profile)); - } - ret.append("?Subject="+urlEncode(subject)); - if(ret.length() > MAX_MAILTO_CHARACTER_COUNT) { - ret.setLength(0); - ret.append("mailto:"); - for (UserProfile profile : profiles) { - if(ret.length()>7) ret.append(','); - ret.append(profile.getEmail().trim()); - } - ret.append("?Subject="+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); - } - } + 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("*"); + } + + @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)); + } + + @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)); + + Map context = defaultVelocityContext(); + context.put("date", new DateTool()); + context.put("markdown", markdown); + + 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()); + } + + 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, 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); + } + } + + private boolean hideSignatures(Map params, Signature signature, String currentUserName) { + boolean pendingVisible = isVisible(signature, currentUserName, params.get("pendingVisible")); + boolean signaturesVisible = isVisible(signature, currentUserName, params.get("signaturesVisible")); + if (!pendingVisible) signature.setMissingSignatures(emptySet()); + if (!signaturesVisible) signature.setSignatures(emptyMap()); + return pendingVisible && signaturesVisible; + } + + 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 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!"); + } + 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(); + } + 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 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(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, 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); + } + } } diff --git a/src/main/java/com/baloise/confluence/digitalsignature/InheritSigners.java b/src/main/java/com/baloise/confluence/digitalsignature/InheritSigners.java index 2e6b510..c603032 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 - ; - - public static InheritSigners ofValue(String v) { - try { - return InheritSigners.valueOf(v.toUpperCase().replaceAll("\\W+", "_")); - } catch (Exception e) { - return NONE; - } - - } + 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; + } + } } diff --git a/src/main/java/com/baloise/confluence/digitalsignature/Markdown.java b/src/main/java/com/baloise/confluence/digitalsignature/Markdown.java index b2855c4..abf2f99 100644 --- a/src/main/java/com/baloise/confluence/digitalsignature/Markdown.java +++ b/src/main/java/com/baloise/confluence/digitalsignature/Markdown.java @@ -5,9 +5,8 @@ import com.vladsch.flexmark.util.data.MutableDataSet; public class Markdown { - - private Parser parser; - private HtmlRenderer renderer; + private final Parser parser; + private final HtmlRenderer renderer; public Markdown() { MutableDataSet options = new MutableDataSet(); diff --git a/src/main/java/com/baloise/confluence/digitalsignature/Signature.java b/src/main/java/com/baloise/confluence/digitalsignature/Signature.java index 43dede7..64f588e 100644 --- a/src/main/java/com/baloise/confluence/digitalsignature/Signature.java +++ b/src/main/java/com/baloise/confluence/digitalsignature/Signature.java @@ -1,162 +1,194 @@ package com.baloise.confluence.digitalsignature; -import static org.apache.commons.codec.digest.DigestUtils.sha256Hex; - import java.io.Serializable; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; +import java.util.*; + +import static org.apache.commons.codec.digest.DigestUtils.sha256Hex; public class Signature implements Serializable { - - 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 Map signatures = new HashMap(); - private Set missingSignatures = new TreeSet(); - private Set notified = new TreeSet(); - - public Signature() {} - public Signature(long pageId, String body,String title) { - this.pageId = pageId; - this.body = body; - this.title = title == null ? "" : title; - hash = sha256Hex(pageId +":"+ title +":" + body); - key = "signature."+hash; - } - public String getHash() { - if(hash == null) { - hash = getKey().replace("signature.", ""); - } - return hash; - } - public void setHash(String hash) { - this.hash = hash; - } - public String getKey() { - return key; - } - public String getProtectedKey() { - return "protected."+getHash(); - } - public void setKey(String key) { - this.key = key; - } - public long getPageId() { - return pageId; - } - public void setPageId(long pageId) { - this.pageId = pageId; - } - public String getBody() { - return body; - } - public void setBody(String body) { - this.body = body; - } - public Map getSignatures() { - return signatures; - } - public void setSignatures(Map signatures) { - this.signatures = signatures; - } - public Set getMissingSignatures() { - return missingSignatures; - } - public void setMissingSignatures(Set missingSignatures) { - this.missingSignatures = missingSignatures; - } - public long getMaxSignatures() { - return maxSignatures; - } - public void setMaxSignatures(long maxSignatures) { - this.maxSignatures = maxSignatures; - } - public String getTitle() { - return title; - } - public void setTitle(String title) { - this.title = title; - } - public Set getNotify() { - return notified; - } - public void setNotify(Set notify) { - this.notified = notify; - } - @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; - Signature other = (Signature) obj; - if (key == null) { - if (other.key != null) - return false; - } else if (!key.equals(other.key)) - return false; - return true; - } - - public Signature withNotified(Set notified) { - this.notified = notified; - return this; - } - - public Signature withMaxSignatures(long maxSignatures) { - this.maxSignatures = maxSignatures; - return this; - } - - public boolean hasSigned(String userName) { - return signatures.containsKey(userName); - } - - public boolean isPetitionMode() { - return isPetitionMode(getMissingSignatures()); - } - - public static boolean isPetitionMode(Set userGroups) { - return userGroups != null && userGroups.size() == 1 && userGroups.iterator().next().trim().equals("*"); - } - public boolean sign(String userName) { - if(!isMaxSignaturesReached() && !isPetitionMode() && !getMissingSignatures().remove(userName)) { - return false; - } else { - 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()); - } + + 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 notified = new TreeSet<>(); + + public Signature() { + } + + public Signature(long pageId, String body, String title) { + this.pageId = pageId; + this.body = body; + this.title = title == null ? "" : title; + hash = sha256Hex(pageId + ":" + title + ":" + body); + key = "signature." + hash; + } + + public static boolean isPetitionMode(Set userGroups) { + return userGroups != null && userGroups.size() == 1 && userGroups.iterator().next().trim().equals("*"); + } + + public String getHash() { + if (hash == null) { + hash = getKey().replace("signature.", ""); + } + return hash; + } + + public void setHash(String hash) { + this.hash = hash; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getProtectedKey() { + return "protected." + getHash(); + } + + public long getPageId() { + return pageId; + } + + public void setPageId(long pageId) { + this.pageId = pageId; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public Map getSignatures() { + return signatures; + } + + public void setSignatures(Map signatures) { + this.signatures = signatures; + } + + public Set getMissingSignatures() { + return missingSignatures; + } + + public void setMissingSignatures(Set missingSignatures) { + this.missingSignatures = missingSignatures; + } + + public long getVisibilityLimit() { + return visibilityLimit; + } + + public void setVisibilityLimit(long visibilityLimit) { + this.visibilityLimit = visibilityLimit; + } + + public long getMaxSignatures() { + return maxSignatures; + } + + public void setMaxSignatures(long maxSignatures) { + this.maxSignatures = maxSignatures; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Set getNotify() { + return notified; + } + + public void setNotify(Set notify) { + this.notified = notify; + } + + @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; + Signature other = (Signature) obj; + if (key == null) { + return other.key == null; + } else return key.equals(other.key); + } + + public Signature withNotified(Set notified) { + this.notified = notified; + return this; + } + + public Signature withMaxSignatures(long maxSignatures) { + this.maxSignatures = maxSignatures; + return this; + } + + public Signature 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; + } else { + 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 74b0bba..417b28b 100644 --- a/src/main/java/com/baloise/confluence/digitalsignature/SignaturesVisible.java +++ b/src/main/java/com/baloise/confluence/digitalsignature/SignaturesVisible.java @@ -1,16 +1,15 @@ package com.baloise.confluence.digitalsignature; public enum SignaturesVisible { - ALWAYS, IF_SIGNATORY, IF_SIGNED - ; - - public static SignaturesVisible ofValue(String v) { - try { - return SignaturesVisible.valueOf(v.toUpperCase().replaceAll("\\W+", "_")); - } catch (Exception e) { - return ALWAYS; - } - - } + ALWAYS, + IF_SIGNATORY, + IF_SIGNED; + 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 0700d60..ea02d57 100644 --- a/src/main/java/com/baloise/confluence/digitalsignature/UserProfileByName.java +++ b/src/main/java/com/baloise/confluence/digitalsignature/UserProfileByName.java @@ -1,23 +1,26 @@ package com.baloise.confluence.digitalsignature; -import java.util.Comparator; - import com.atlassian.sal.api.user.UserProfile; +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; - 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; - return Integer.compare(u1.hashCode(), u2.hashCode()); - } - - private String nn(String string) { - return string == null ? "" : string; - } -} \ No newline at end of file + + @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.getUsername()).compareTo(nn(u2.getUsername())); + if (ret != 0) return ret; + + return Integer.compare(u1.hashCode(), u2.hashCode()); + } + + 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 178313e..3825a8e 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 -{ - public final static String PLUGIN_KEY = "com.baloise.confluence:digital-signature"; - String getName(); -} \ No newline at end of file +public interface DigitalSignatureComponent { + String PLUGIN_KEY = "com.baloise.confluence:digital-signature"; + + 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 03a6840..379b812 100644 --- a/src/main/java/com/baloise/confluence/digitalsignature/impl/DigitalSignatureComponentImpl.java +++ b/src/main/java/com/baloise/confluence/digitalsignature/impl/DigitalSignatureComponentImpl.java @@ -8,29 +8,26 @@ import javax.inject.Inject; import javax.inject.Named; -@ExportAsService ({DigitalSignatureComponent.class}) -@Named ("digitalSignatureComponent") -public class DigitalSignatureComponentImpl implements DigitalSignatureComponent -{ +@ExportAsService({DigitalSignatureComponent.class}) +@Named("digitalSignatureComponent") +public class DigitalSignatureComponentImpl implements DigitalSignatureComponent { @ComponentImport private final ApplicationProperties applicationProperties; public DigitalSignatureComponentImpl() { - this(null); + this(null); } + @Inject - public DigitalSignatureComponentImpl(final ApplicationProperties applicationProperties) - { + public DigitalSignatureComponentImpl(final ApplicationProperties applicationProperties) { this.applicationProperties = applicationProperties; } - public String getName() - { - if(null != applicationProperties) - { + public String getName() { + if (null != applicationProperties) { return "digitalSignatureComponent:" + applicationProperties.getDisplayName(); } - + return "digitalSignatureComponent"; } -} \ No newline at end of file +} diff --git a/src/main/java/com/baloise/confluence/digitalsignature/rest/DigitalSigatureService.java b/src/main/java/com/baloise/confluence/digitalsignature/rest/DigitalSigatureService.java index 7bab347..433bca9 100644 --- a/src/main/java/com/baloise/confluence/digitalsignature/rest/DigitalSigatureService.java +++ b/src/main/java/com/baloise/confluence/digitalsignature/rest/DigitalSigatureService.java @@ -1,36 +1,4 @@ package com.baloise.confluence.digitalsignature.rest; -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; - -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 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 org.apache.velocity.tools.generic.DateTool; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import com.atlassian.bandana.BandanaManager; import com.atlassian.confluence.pages.Page; @@ -53,170 +21,192 @@ 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 e) { - log.error("Could not send notification to "+notifiedUser, e); - } catch (InterruptedException e) { - log.error("Could not send notification to "+notifiedUser, e); - } catch (MailException e) { - log.error("Could not send notification to "+notifiedUser, e); - } catch (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 context = defaultVelocityContext(); - context.put("signature", signature); - context.put("markdown", markdown); - 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("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(); - } - -} \ No newline at end of file + 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/sal/DummyProfile.java b/src/main/java/com/baloise/confluence/digitalsignature/sal/DummyProfile.java index ebe1051..ac26213 100644 --- a/src/main/java/com/baloise/confluence/digitalsignature/sal/DummyProfile.java +++ b/src/main/java/com/baloise/confluence/digitalsignature/sal/DummyProfile.java @@ -1,51 +1,51 @@ package com.baloise.confluence.digitalsignature.sal; -import java.net.URI; - import com.atlassian.sal.api.user.UserKey; import com.atlassian.sal.api.user.UserProfile; +import java.net.URI; + 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 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 b1f0519..8f0fba7 100644 --- a/src/main/resources/META-INF/spring/plugin-context.xml +++ b/src/main/resources/META-INF/spring/plugin-context.xml @@ -7,4 +7,4 @@ http://www.atlassian.com/schema/atlassian-scanner http://www.atlassian.com/schema/atlassian-scanner/atlassian-scanner.xsd"> - \ No newline at end of file + diff --git a/src/main/resources/atlassian-plugin.xml b/src/main/resources/atlassian-plugin.xml index e3f34ac..ef33be4 100644 --- a/src/main/resources/atlassian-plugin.xml +++ b/src/main/resources/atlassian-plugin.xml @@ -1,66 +1,64 @@ - - ${project.description} - ${project.version} - - images/pluginIcon.png - images/pluginLogo.png - + plugins-version="2"> + + ${project.description} + ${project.version} + + images/pluginIcon.png + images/pluginLogo.png + - - - - - - - com.atlassian.auiplugin:ajs + + + - - - - digital-signature - + + + com.atlassian.auiplugin:ajs - + + + + digital-signature + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Provides signature services. com.baloise.confluence.digitalsignature.rest diff --git a/src/main/resources/css/digital-signature.css b/src/main/resources/css/digital-signature.css index e69de29..a0d8fb9 100644 --- a/src/main/resources/css/digital-signature.css +++ b/src/main/resources/css/digital-signature.css @@ -0,0 +1,28 @@ +.panel { + border-width: 1px; +} + +.panel .panelHeader { + border-bottom-width: 1px; + color: #bbb; +} + +.panel .panelHeader .aui-buttons { + float: right; +} + +.panel .panelHeader .aui-icon.aui-icon-small.aui-iconfont-attachment { + margin-right: 20px; +} + +.panel .panelHeader .aui-icon.aui-icon-small.aui-iconfont-file-pdf { + float: right; +} + +.body-list { + list-style-type: none; +} + +.body-list .body-list-item { + color: #bbb; +} diff --git a/src/main/resources/digital-signature.properties b/src/main/resources/digital-signature.properties index eb3e6f3..26f012b 100644 --- a/src/main/resources/digital-signature.properties +++ b/src/main/resources/digital-signature.properties @@ -4,7 +4,7 @@ com.baloise.confluence.digital-signature.signature.desc=Embeds a signature panel com.baloise.confluence.digital-signature.signature.param.title.label=Panel Title com.baloise.confluence.digital-signature.signature.param.title.desc=Text displayed at the top of the signature panel. com.baloise.confluence.digital-signature.signature.param.signers.label=Signatories -com.baloise.confluence.digital-signature.signature.param.signers.desc=Users who can sign the text within the panel, in addition to members of signatory groups (below) and signatories inherited from page permissions (below). +com.baloise.confluence.digital-signature.signature.param.signers.desc=Users who can sign the text within the panel, in addition to members of signatory groups (below) and signatories inherited from page permissions (below). com.baloise.confluence.digital-signature.signature.param.signerGroups.label=Signatory Groups com.baloise.confluence.digital-signature.signature.param.signerGroups.desc=Comma-separated list of Confluence groups whose members may sign the panel in addition to named signatories (above) and signatories inherited from page permissions (below). Group names must be entered exactly (e.g. group-name). Fill in * (asterisk) to permit any logged-in user who may view the page to sign. com.baloise.confluence.digital-signature.signature.param.inheritSigners.label=Inherit Signatories from Page Permissions @@ -23,6 +23,7 @@ com.baloise.confluence.digital-signature.signature.param.pendingVisible.desc= com.baloise.confluence.digital-signature.signature.param.pendingVisible.always.desc=always com.baloise.confluence.digital-signature.signature.param.pendingVisible.if\ signatory.desc=if user is allowed to sign com.baloise.confluence.digital-signature.signature.param.pendingVisible.if\ signed.desc=if user has signed +com.baloise.confluence.digital-signature.signature.param.visibilityLimit.label=Limit of visible signees com.baloise.confluence.digital-signature.signature.param.maxSignatures.label=Maximum Signatures com.baloise.confluence.digital-signature.signature.param.maxSignatures.desc=Maximum number of signatures accepted on the panel. (Fill in 0 to disable signing without modifying existing signatures.) com.baloise.confluence.digital-signature.signature.param.notified.label=Notified Users @@ -32,6 +33,7 @@ com.baloise.confluence.digital-signature.signature.param.panel.desc=Show a visib com.baloise.confluence.digital-signature.signature.param.protectedContent.label=Create Protected Child Page com.baloise.confluence.digital-signature.signature.param.protectedContent.desc=Create a protected child page underneath the current page, which can only be viewed+edited by users who have permission to edit the current page, and viewed by users who have signed the panel. Because the protected child page uses the same key as the panel, you should finalise the panel title and text and only then check this option to generate the protected child page. com.baloise.confluence.digital-signature.signature.macro.button.sign-as.label=Sign as {0} +com.baloise.confluence.digital-signature.signature.macro.button.show-all.label=Show all com.baloise.confluence.digital-signature.signature.macro.panel.protected-content.label=Go to protected page com.baloise.confluence.digital-signature.signature.macro.panel.export.label=Export signature panel com.baloise.confluence.digital-signature.signature.macro.panel.email.label=Send e-mail to signatories diff --git a/src/main/resources/digital-signature_de.properties b/src/main/resources/digital-signature_de.properties index 22395a4..1d6637d 100644 --- a/src/main/resources/digital-signature_de.properties +++ b/src/main/resources/digital-signature_de.properties @@ -4,7 +4,7 @@ com.baloise.confluence.digital-signature.signature.desc=F com.baloise.confluence.digital-signature.signature.param.title.label=Titel com.baloise.confluence.digital-signature.signature.param.title.desc=Titel des unterzeichneten Bereichs. com.baloise.confluence.digital-signature.signature.param.signers.label=Unterzeichnende Benutzer -com.baloise.confluence.digital-signature.signature.param.signers.desc=Einzelne Benutzer, welche den Bereich unterzeichnen knnen. Zustzlich zu den unterzeichnenden Gruppen und aus Seitenbeschrnkungen vererbten Unterzeichnenden (siehe Unten). +com.baloise.confluence.digital-signature.signature.param.signers.desc=Einzelne Benutzer, welche den Bereich unterzeichnen knnen. Zustzlich zu den unterzeichnenden Gruppen und aus Seitenbeschrnkungen vererbten Unterzeichnenden (siehe Unten). com.baloise.confluence.digital-signature.signature.param.signerGroups.label=Unterzeichnende Gruppen com.baloise.confluence.digital-signature.signature.param.signerGroups.desc=Kommagetrennte Liste von Confluence Gruppen deren Mitglieder den Bereich unterzeichnen knnen. Zustzlich zu den unterzeichnenden Benutzern (siehe oben) und aus Seitenbeschrnkungen vererbten Unterzeichnenden (siehe Unten). Gruppennamen mssen exakt eingegeben werden (z.B. Gruppen-Name). Geben Sie * (Stern) ein um allen angemeldeten Benutzern die Unterschrift zu ermglichen. com.baloise.confluence.digital-signature.signature.param.inheritSigners.label=Aus Seitenbeschrnkungen vererbte Unterzeichnende @@ -23,6 +23,7 @@ com.baloise.confluence.digital-signature.signature.param.pendingVisible.desc= com.baloise.confluence.digital-signature.signature.param.pendingVisible.always.desc=immer com.baloise.confluence.digital-signature.signature.param.pendingVisible.if\ signatory.desc=den Unterzeichnenden com.baloise.confluence.digital-signature.signature.param.pendingVisible.if\ signed.desc=nach der Unterschrift +com.baloise.confluence.digital-signature.signature.param.visibilityLimit.label=Maximal sichtbare Unterschriften com.baloise.confluence.digital-signature.signature.param.maxSignatures.label=Maximale Anzahl Unterschriften com.baloise.confluence.digital-signature.signature.param.maxSignatures.desc=Geben Sie 0 ein um das Unterzeichnen zu verhindern, ohne bestehnende Unterschriften zu beeinflussen. com.baloise.confluence.digital-signature.signature.param.notified.label=Benachrichtige Benutzer @@ -32,6 +33,7 @@ com.baloise.confluence.digital-signature.signature.param.panel.desc=Einen Rahmen com.baloise.confluence.digital-signature.signature.param.protectedContent.label=Geschtzte untergeordnete Seite erstellen com.baloise.confluence.digital-signature.signature.param.protectedContent.desc=Erstellt einge geschtzte Unterseite zum geschtzten Bereich. Jeder Unterzeichnende bekommt automatisch Leserechte auf der geschtzen Unterseite. Da die Unterseite den selben Schlssel verwendet wie der unterzeichnete Bereich, sollten Sie erst Titel und Inhalt des Bereichs fertigstellen, bevor die Unterseite aktiviert wird. com.baloise.confluence.digital-signature.signature.macro.button.sign-as.label=Unterzeichnen als {0} +com.baloise.confluence.digital-signature.signature.macro.button.show-all.label=Alle anzeigen com.baloise.confluence.digital-signature.signature.macro.panel.protected-content.label=Geschtzte Seite aufrufen com.baloise.confluence.digital-signature.signature.macro.panel.export.label=Geschtzten Bereicht exportieren com.baloise.confluence.digital-signature.signature.macro.panel.email.label=Email an Unterzeichnende @@ -42,6 +44,6 @@ com.baloise.confluence.digital-signature.signature.macro.warning.editPermissionR com.baloise.confluence.digital-signature.signature.service.error.badUser=Es wird nicht erwartet, dass {0} {1} unterzeichnet. com.baloise.confluence.digital-signature.signature.service.message.label.withNames=Mit Namen com.baloise.confluence.digital-signature.signature.service.message.hasSignedShort={0} hat {1} unterzeichnet. -com.baloise.confluence.digital-signature.signature.service.message.signedUsersEmails=Emailadressen der Benutzer, welche {2} {0}unterzeichnet{1} haben +com.baloise.confluence.digital-signature.signature.service.message.signedUsersEmails=Emailadressen der Benutzer, welche {2} {0}unterzeichnet{1} haben com.baloise.confluence.digital-signature.signature.service.message.unsignedUsersEmails=Emailadressen der Benutzer, welche {2} {0}nicht unterzeichnet{1} haben -com.baloise.confluence.digital-signature.signature.service.warning.maxSignaturesReached=Die maximale Anzahl von {0} Unterschriften wurde erreicht. \ No newline at end of file +com.baloise.confluence.digital-signature.signature.service.warning.maxSignaturesReached=Die maximale Anzahl von {0} Unterschriften wurde erreicht. diff --git a/src/main/resources/digital-signature_ja_JP.properties b/src/main/resources/digital-signature_ja_JP.properties index 7b4469a..67d9ce9 100644 --- a/src/main/resources/digital-signature_ja_JP.properties +++ b/src/main/resources/digital-signature_ja_JP.properties @@ -15,6 +15,7 @@ com.baloise.confluence.digital-signature.signature.param.inheritSigners.none.des com.baloise.confluence.digital-signature.signature.param.inheritSigners.readers\ only.desc=\u8aad\u307f\u53d6\u6a29\u9650\u30e6\u30fc\u30b6\u30fc\u3060\u3051 com.baloise.confluence.digital-signature.signature.param.inheritSigners.writers\ only.desc=\u7de8\u96c6\u6a29\u9650\u30e6\u30fc\u30b6\u30fc\u3060\u3051 com.baloise.confluence.digital-signature.signature.param.inheritSigners.readers\ and\ writers.desc=\u305d\u306e\u53cc\u65b9\u6a29\u9650\u30e6\u30fc\u30b6\u30fc +com.baloise.confluence.digital-signature.signature.param.visibilityLimit.label=\u8868\u793a\u3055\u308c\u308b\u7f72\u540d\u8005\u306e\u5236\u9650 com.baloise.confluence.digital-signature.signature.param.maxSignatures.label=\u6700\u591a\u7f72\u540d\u6570 com.baloise.confluence.digital-signature.signature.param.maxSignatures.desc=\u30d1\u30cd\u30eb\u4e0a\u3067\u8a8d\u3081\u3089\u308c\u308b\u6700\u591a\u7f72\u540d\u6570\u3002(\u65e2\u5b58\u7f72\u540d\u306e\u5909\u66f4\u306a\u3057\u3067\u306e\u7f72\u540d\u3092\u7121\u52b9\u306b\u3059\u308b\u306b\u306f\u30010\u3092\u8a18\u5165\u3057\u3066\u304f\u3060\u3055\u3044\u3002) com.baloise.confluence.digital-signature.signature.param.notified.label=\u901a\u77e5\u3055\u308c\u308b\u30e6\u30fc\u30b6\u30fc @@ -24,6 +25,7 @@ com.baloise.confluence.digital-signature.signature.param.panel.desc=\u7f72\u540d com.baloise.confluence.digital-signature.signature.param.protectedContent.label=\u4fdd\u8b77\u3055\u308c\u305f\u5b50\u30da\u30fc\u30b8\u3092\u4f5c\u6210\u3059\u308b com.baloise.confluence.digital-signature.signature.param.protectedContent.desc=\u73fe\u5728\u30da\u30fc\u30b8\u306e\u4e0b\u306b\u4fdd\u8b77\u3055\u308c\u305f\u5b50\u30da\u30fc\u30b8\u3092\u4f5c\u6210\u3057\u307e\u3059\u3002\u73fe\u5728\u30da\u30fc\u30b8\u306e\u7de8\u96c6\u6a29\u9650\u3092\u6709\u3059\u308b\u30e6\u30fc\u30b6\u30fc\u304c\u3001\u3053\u308c\u3092\u95b2\u89a7\u304a\u3088\u3073\u7de8\u96c6\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u3001\u30d1\u30cd\u30eb\u3092\u7f72\u540d\u3057\u305f\u30e6\u30fc\u30b6\u30fc\u304c\u3001\u3053\u308c\u3092\u95b2\u89a7\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u3059\u3002\u4fdd\u8b77\u3055\u308c\u305f\u5b50\u30da\u30fc\u30b8\u306f\u30d1\u30cd\u30eb\u3068\u540c\u3058\u30ad\u30fc\u3092\u4f7f\u3046\u305f\u3081\u3001\u30d1\u30cd\u30eb\u30bf\u30a4\u30c8\u30eb\u3068\u30c6\u30ad\u30b9\u30c8\u306e\u5165\u529b\u3092\u5b8c\u4e86\u3057\u3001\u305d\u306e\u5f8c\u3053\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u306b\u30c1\u30a7\u30c3\u30af\u3092\u5165\u308c\u3066\u304b\u3089\u3001\u4fdd\u8b77\u3055\u308c\u305f\u5b50\u30da\u30fc\u30b8\u3092\u751f\u6210\u3059\u308b\u5fc5\u8981\u304c\u308a\u307e\u3059\u3002 com.baloise.confluence.digital-signature.signature.macro.button.sign-as.label={0}\u3068\u3057\u3066\u7f72\u540d\u3059\u308b +com.baloise.confluence.digital-signature.signature.macro.button.show-all.label=\u3059\u3079\u3066\u8868\u793a\u3059\u308b com.baloise.confluence.digital-signature.signature.macro.panel.protected-content.label=\u4fdd\u8b77\u3055\u308c\u305f\u30da\u30fc\u30b8\u3078\u9032\u3080 com.baloise.confluence.digital-signature.signature.macro.panel.export.label=\u7f72\u540d\u30d1\u30cd\u30eb\u3092\u30a8\u30af\u30b9\u30dd\u30fc\u30c8\u3059\u308b com.baloise.confluence.digital-signature.signature.macro.panel.email.label=\u7f72\u540d\u8005\u306bE\u30e1\u30fc\u30eb\u3092\u9001\u308b diff --git a/src/main/resources/digital-signature_ja_JP.properties.utf8 b/src/main/resources/digital-signature_ja_JP.properties.utf8 index bb804b6..737989d 100644 --- a/src/main/resources/digital-signature_ja_JP.properties.utf8 +++ b/src/main/resources/digital-signature_ja_JP.properties.utf8 @@ -15,6 +15,7 @@ com.baloise.confluence.digital-signature.signature.param.inheritSigners.none.des com.baloise.confluence.digital-signature.signature.param.inheritSigners.readers\ only.desc=読み取権限ユーザーだけ com.baloise.confluence.digital-signature.signature.param.inheritSigners.writers\ only.desc=編集権限ユーザーだけ com.baloise.confluence.digital-signature.signature.param.inheritSigners.readers\ and\ writers.desc=その双方権限ユーザー +com.baloise.confluence.digital-signature.signature.param.visibilityLimit.label=表示される署名者の制限 com.baloise.confluence.digital-signature.signature.param.maxSignatures.label=最多署名数 com.baloise.confluence.digital-signature.signature.param.maxSignatures.desc=パネル上で認められる最多署名数。(既存署名の変更なしでの署名を無効にするには、0を記入してください。) com.baloise.confluence.digital-signature.signature.param.notified.label=通知されるユーザー @@ -24,6 +25,7 @@ com.baloise.confluence.digital-signature.signature.param.panel.desc=署名され com.baloise.confluence.digital-signature.signature.param.protectedContent.label=保護された子ページを作成する com.baloise.confluence.digital-signature.signature.param.protectedContent.desc=現在ページの下に保護された子ページを作成します。現在ページの編集権限を有するユーザーが、これを閲覧および編集することができ、パネルを署名したユーザーが、これを閲覧することができます。保護された子ページはパネルと同じキーを使うため、パネルタイトルとテキストの入力を完了し、その後このオプションにチェックを入れてから、保護された子ページを生成する必要がります。 com.baloise.confluence.digital-signature.signature.macro.button.sign-as.label={0}として署名する +com.baloise.confluence.digital-signature.signature.macro.button.show-all.label=すべて表示する com.baloise.confluence.digital-signature.signature.macro.panel.protected-content.label=保護されたページへ進む com.baloise.confluence.digital-signature.signature.macro.panel.export.label=署名パネルをエクスポートする com.baloise.confluence.digital-signature.signature.macro.panel.email.label=署名者にEメールを送る diff --git a/src/main/resources/js/digital-signature.js b/src/main/resources/js/digital-signature.js index e69de29..fc75990 100644 --- a/src/main/resources/js/digital-signature.js +++ b/src/main/resources/js/digital-signature.js @@ -0,0 +1,43 @@ +function hideElements($ul, limit) { + 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; + } + + return isSomethingHidden; +} + +function showAllElements($ul) { + $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); + }); + } +} diff --git a/src/main/resources/templates/email.vm b/src/main/resources/templates/email.vm index bbdaa3f..0ae5326 100644 --- a/src/main/resources/templates/email.vm +++ b/src/main/resources/templates/email.vm @@ -1,12 +1,14 @@ #set( $dateFormatter = $action.getDateFormatter()) $signedOrNotWithHtml
- +
- +
-#foreach( $email in $emails)
+    #foreach( $email in $emails)
 $email
 #end
 
diff --git a/src/main/resources/templates/export.vm b/src/main/resources/templates/export.vm index e357750..dae8695 100644 --- a/src/main/resources/templates/export.vm +++ b/src/main/resources/templates/export.vm @@ -1,31 +1,32 @@ #set( $dateFormatter = $action.getDateFormatter())

$signature.getTitle()

#set($bodyWithHtml = $markdown.toHTML($signature.getBody()))

$bodyWithHtml

-#foreach ($date2userName in $orderedSignatures) - #set( $userName = $date2userName.key) - #set( $profile = $profiles.get($userName)) - - - - - -#end -#foreach( $profile in $orderedMissingSignatureProfiles) - - - - - -#end + #foreach ($date2userName in $orderedSignatures) + #set( $userName = $date2userName.key) + #set( $profile = $profiles.get($userName)) + + + + + + #end + #foreach( $profile in $orderedMissingSignatureProfiles) + + + + + + #end
$dateFormatter.formatDateTime($date2userName.value)$profile.getFullName()$profile.getEmail()
$profile.getFullName()$profile.getEmail()
$dateFormatter.formatDateTime($date2userName.value)$profile.getFullName()$profile.getEmail()
$profile.getFullName()$profile.getEmail()
diff --git a/src/main/resources/templates/macro.vm b/src/main/resources/templates/macro.vm index 492fb64..be6ef39 100644 --- a/src/main/resources/templates/macro.vm +++ b/src/main/resources/templates/macro.vm @@ -1,51 +1,85 @@ -#set( $dateFormatter = $action.getDateFormatter()) -#set( $title = $signature.getTitle()) -#if( $panel ) -
- +#requireResource("com.baloise.confluence.digital-signature:digital-signature-resources") + +#set($dateFormatter = $action.getDateFormatter()) +#set($title = $signature.getTitle()) +#set($macroId = $signature.getKey().replace("signature.", "")) +#if($panel) +
+
#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 -
-#if( $panel ) +

$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 baef0e8..44bb605 100644 --- a/src/test/java/com/baloise/confluence/digitalsignature/DigitalSignatureMacroTest.java +++ b/src/test/java/com/baloise/confluence/digitalsignature/DigitalSignatureMacroTest.java @@ -1,65 +1,61 @@ package com.baloise.confluence.digitalsignature; -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - import java.util.ArrayList; import java.util.List; import org.junit.Test; -import org.mockito.Mockito; import com.atlassian.confluence.setup.BootstrapManager; import com.atlassian.sal.api.user.UserProfile; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + public class DigitalSignatureMacroTest { - Signature signature = new Signature(1, "test", "title"); - - BootstrapManager bootstrapManager = mock(BootstrapManager.class); - - - @Test - public void getMailtoLong() { - DigitalSignatureMacro macro = new DigitalSignatureMacro(null, null, null, null, null, null, null); - List profiles = new ArrayList(); - UserProfile profile = Mockito.mock(UserProfile.class); - when(profile.getFullName()).thenReturn("Heinz Meier"); - when(profile.getEmail()).thenReturn("heinz.meier@meier.com"); - 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() { - Mockito.when(bootstrapManager.getWebAppContextPath()).thenReturn("nirvana"); - - DigitalSignatureMacro macro = new DigitalSignatureMacro(null, null, bootstrapManager, null, null, null, null); - List profiles = new ArrayList(); - UserProfile profile = Mockito.mock(UserProfile.class); - when(profile.getFullName()).thenReturn("Heinz Meier"); - when(profile.getEmail()).thenReturn("heinz.meier@meier.com"); - 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); - List profiles = new ArrayList(); - UserProfile profile = Mockito.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); - } - + private final Signature signature = new Signature(1, "test", "title"); + private final BootstrapManager bootstrapManager = mock(BootstrapManager.class); + + @Test + public void getMailtoLong() { + DigitalSignatureMacro macro = new DigitalSignatureMacro(null, 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"); + 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() { + when(bootstrapManager.getWebAppContextPath()).thenReturn("nirvana"); + + DigitalSignatureMacro macro = new DigitalSignatureMacro(null, null, bootstrapManager, 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"); + 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); + 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 f77ee58..3d60ce5 100644 --- a/src/test/java/com/baloise/confluence/digitalsignature/InheritSignersTest.java +++ b/src/test/java/com/baloise/confluence/digitalsignature/InheritSignersTest.java @@ -1,23 +1,25 @@ package com.baloise.confluence.digitalsignature; -import static com.baloise.confluence.digitalsignature.InheritSigners.*; -import static org.junit.Assert.assertEquals; - 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; + public class InheritSignersTest { + @Test + public void testOfValueReadersOnly() { + assertEquals(READERS_ONLY, ofValue("readers only")); + } - @Test - public void READERS_ONLY() throws Exception { - assertEquals(READERS_ONLY, ofValue("readers only")); - } - @Test - public void NONE_NULL() throws Exception { - assertEquals(NONE, ofValue(null)); - } - @Test - public void NONE_IllegalArgument() throws Exception { - assertEquals(NONE, ofValue("asdasd")); - } + @Test + public void testOfValueNoneNull() { + assertEquals(NONE, ofValue(null)); + } + @Test + public 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 0604eb5..e87b7a6 100644 --- a/src/test/java/com/baloise/confluence/digitalsignature/MarkdownTest.java +++ b/src/test/java/com/baloise/confluence/digitalsignature/MarkdownTest.java @@ -1,40 +1,33 @@ package com.baloise.confluence.digitalsignature; -import static java.nio.file.Files.readAllLines; -import static java.nio.file.Paths.get; -import static java.util.stream.Collectors.joining; -import static org.junit.Assert.assertEquals; +import org.junit.Before; +import org.junit.Test; import java.io.IOException; import java.net.URISyntaxException; -import org.junit.Before; -import org.junit.Test; - +import static java.nio.file.Files.readAllLines; +import static java.nio.file.Paths.get; +import static org.junit.Assert.assertEquals; public class MarkdownTest { - - - Markdown markdown; - - @Before - public 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()); - } - - - private String readResource(String name) throws IOException, URISyntaxException { - return readAllLines(get(getClass().getResource("/"+name).toURI())).stream().collect(joining("\n")); - } - + private Markdown markdown; + + @Before + public 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()); + } + + 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 09a2610..4d2dde1 100644 --- a/src/test/java/com/baloise/confluence/digitalsignature/MessageFormatTest.java +++ b/src/test/java/com/baloise/confluence/digitalsignature/MessageFormatTest.java @@ -1,21 +1,19 @@ package com.baloise.confluence.digitalsignature; -import static org.junit.Assert.*; - import java.text.MessageFormat; import org.junit.Test; -public class MessageFormatTest { - - @Test - public void test() { - 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"); - assertEquals("#123 was signed", actual); - } +import static org.junit.Assert.assertEquals; +public class MessageFormatTest { + @Test + public void test() { + 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"); + 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 e5ee2dc..165ce7f 100644 --- a/src/test/java/com/baloise/confluence/digitalsignature/SignatureSerialisationTest.java +++ b/src/test/java/com/baloise/confluence/digitalsignature/SignatureSerialisationTest.java @@ -1,7 +1,6 @@ package com.baloise.confluence.digitalsignature; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import org.junit.Test; import java.io.FileOutputStream; import java.io.IOException; @@ -9,35 +8,34 @@ import java.io.ObjectOutputStream; import java.util.Date; -import org.junit.Test; - -import jdk.nashorn.internal.ir.annotations.Ignore; +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()); + } - @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()); - } - - @Test - @Ignore - public void serialise() throws IOException { - 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.ser")); - out.writeObject(signature); - out.close(); - } + @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()); + } } diff --git a/src/test/java/com/baloise/confluence/digitalsignature/TemplatesTest.java b/src/test/java/com/baloise/confluence/digitalsignature/TemplatesTest.java index 8bbe1d4..e4930a3 100644 --- a/src/test/java/com/baloise/confluence/digitalsignature/TemplatesTest.java +++ b/src/test/java/com/baloise/confluence/digitalsignature/TemplatesTest.java @@ -1,48 +1,46 @@ package com.baloise.confluence.digitalsignature; -import static org.apache.velocity.app.Velocity.mergeTemplate; +import org.apache.velocity.VelocityContext; +import org.junit.Test; import java.io.BufferedWriter; import java.io.StringWriter; import java.io.Writer; -import org.apache.velocity.VelocityContext; -import org.apache.velocity.exception.MethodInvocationException; -import org.apache.velocity.exception.ParseErrorException; -import org.apache.velocity.exception.ResourceNotFoundException; -import org.junit.Test; +import static org.apache.velocity.app.Velocity.mergeTemplate; +import static org.junit.Assert.assertEquals; public class TemplatesTest { + private static String normalize(String input) { + return input.replaceAll("[\n\r]", "") + .replaceAll(" +", " ") + .replaceAll("> <", "><") + .trim(); + } - @Test - public void test() throws ResourceNotFoundException, ParseErrorException, MethodInvocationException, Exception { - StringWriter sw = new StringWriter(); - //let's buffer Writer for better performace: - 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 result = sw.toString(); - System.out.println(result); - } - - @Test - public void test2() throws ResourceNotFoundException, ParseErrorException, MethodInvocationException, Exception { - StringWriter sw = new StringWriter(); - //let's buffer Writer for better performace: - 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 result = sw.toString(); - System.out.println(result); - } - - @Test - public void foo() { - System.out.println("https://test-confluence.baloisenet.com/atlassian/rest/signature/1.0/".split("rest/")[0]); - } + @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 + 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())); + } } diff --git a/src/test/java/com/baloise/confluence/digitalsignature/UserProfileByNameTest.java b/src/test/java/com/baloise/confluence/digitalsignature/UserProfileByNameTest.java index 7d74372..5c731c8 100644 --- a/src/test/java/com/baloise/confluence/digitalsignature/UserProfileByNameTest.java +++ b/src/test/java/com/baloise/confluence/digitalsignature/UserProfileByNameTest.java @@ -1,32 +1,31 @@ package com.baloise.confluence.digitalsignature; -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.when; +import com.atlassian.sal.api.user.UserProfile; +import org.junit.Test; +import org.mockito.Mockito; import java.util.SortedSet; import java.util.TreeSet; -import org.junit.Test; -import org.mockito.Mockito; - -import com.atlassian.sal.api.user.UserProfile; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; public class UserProfileByNameTest { - @Test - public void testCompare() throws Exception { - 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()); - } + @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()); + } } 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 21ae6eb..b3e44db 100644 --- a/src/test/java/ut/com/baloise/confluence/digitalsignature/MyComponentUnitTest.java +++ b/src/test/java/ut/com/baloise/confluence/digitalsignature/MyComponentUnitTest.java @@ -6,12 +6,10 @@ import static org.junit.Assert.assertEquals; -public class MyComponentUnitTest -{ +public class MyComponentUnitTest { @Test - public void testMyName() - { + public void testMyName() { DigitalSignatureComponent component = new DigitalSignatureComponentImpl(null); - assertEquals("names do not match!", "digitalSignatureComponent",component.getName()); + assertEquals("names do not match!", "digitalSignatureComponent", component.getName()); } -} \ No newline at end of file +}