/**
*
*/
package org.docx4j.openpackaging.packages;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.docx4j.openpackaging.exceptions.Docx4JException;
import org.docx4j.openpackaging.exceptions.InvalidFormatException;
import org.docx4j.openpackaging.parts.Part;
import org.docx4j.openpackaging.parts.WordprocessingML.DocumentSettingsPart;
import org.docx4j.openpackaging.parts.WordprocessingML.StyleDefinitionsPart;
import org.docx4j.org.apache.poi.poifs.crypt.HashAlgorithm;
import org.docx4j.utils.CompoundTraversalUtilVisitorCallback;
import org.docx4j.utils.TraversalUtilVisitor;
import org.docx4j.wml.ContentAccessor;
import org.docx4j.wml.P;
import org.docx4j.wml.R;
import org.docx4j.wml.STCryptProv;
import org.docx4j.wml.STDocProtect;
import org.docx4j.wml.Tbl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author jharrop
*
*/
public class ProtectDocument extends ProtectionSettings {
protected static Logger log = LoggerFactory.getLogger(ProtectDocument.class);
public ProtectDocument(WordprocessingMLPackage pkg) {
super(pkg);
}
private WordprocessingMLPackage getPkg() {
return (WordprocessingMLPackage)pkg;
}
/**
* Restrict allowed formatting to specified styles, no password.
*
* @param allowedStyleNames
* @param removedNotAllowedFormatting whether existing usages of styles which aren't allowed are removed
* @param autoFormatOverride
* @param styleLockTheme
* @param styleLockQFSet
* @throws Docx4JException
* @since 3.3.0
*/
public void restrictFormatting(List<String> allowedStyleNames, boolean removedNotAllowedFormatting,
boolean autoFormatOverride, boolean styleLockTheme, boolean styleLockQFSet) throws Docx4JException {
restrictFormatting(allowedStyleNames, removedNotAllowedFormatting,
autoFormatOverride, styleLockTheme, styleLockQFSet,
null, null);
}
/**
* Restrict allowed formatting to specified styles, password protected. Defaults to sha1.
*
* @param allowedStyleNames
* @param removedNotAllowedFormatting whether existing usages of styles which aren't allowed are removed
* @param autoFormatOverride
* @param styleLockTheme
* @param styleLockQFSet
* @param password
* @throws Docx4JException
* @since 3.3.0
*/
public void restrictFormatting(List<String> allowedStyleNames, boolean removedNotAllowedFormatting,
boolean autoFormatOverride, boolean styleLockTheme, boolean styleLockQFSet,
String password) throws Docx4JException {
restrictFormatting(allowedStyleNames, removedNotAllowedFormatting,
autoFormatOverride, styleLockTheme, styleLockQFSet,
password, HashAlgorithm.sha1);
}
/**
* Restrict allowed formatting to specified styles. Specify password and HashAlgorithm
*
* @param allowedStyleNames
* @param removedNotAllowedFormatting whether existing usages of styles which aren't allowed are removed
* @param autoFormatOverride
* @param styleLockTheme
* @param styleLockQFSet
* @param password
* @param hashAlgo
* @throws Docx4JException
* @since 3.3.0
*/
public void restrictFormatting(List<String> allowedStyleNames, boolean removedNotAllowedFormatting,
boolean autoFormatOverride, boolean styleLockTheme, boolean styleLockQFSet,
String password, HashAlgorithm hashAlgo) throws Docx4JException {
if (getPkg().getMainDocumentPart()==null)
throw new Docx4JException("No MainDocumentPart in this WordprocessingMLPackage");
if (getPkg().getMainDocumentPart().getStyleDefinitionsPart()==null)
throw new Docx4JException("No StyleDefinitionsPart in this WordprocessingMLPackage");
Set<String> stylesInUse = getPkg().getMainDocumentPart().getStylesInUse();
// The styles part
StyleDefinitionsPart sdp = getPkg().getMainDocumentPart().getStyleDefinitionsPart();
sdp.protectRestrictFormatting(allowedStyleNames, removedNotAllowedFormatting, stylesInUse);
// TODO: do the same to stylesWithEffects.xml
// The main document part (and other content parts)
if (removedNotAllowedFormatting) {
List<TraversalUtilVisitor> visitors = new ArrayList<TraversalUtilVisitor>();
visitors.add(new VisitorRemovePFormatting(sdp, allowedStyleNames));
visitors.add(new VisitorRemoveRFormatting(sdp, allowedStyleNames));
visitors.add(new VisitorRemoveTableFormatting(sdp, allowedStyleNames));
CompoundTraversalUtilVisitorCallback compound = new CompoundTraversalUtilVisitorCallback(visitors);
for( Part p : getPkg().getParts().getParts().values()) {
if (p instanceof ContentAccessor) {
compound.walkJAXBElements(((ContentAccessor)p).getContent());
}
}
}
// The document settings part
DocumentSettingsPart documentSettingsPart = getPkg().getMainDocumentPart().getDocumentSettingsPart(true);
documentSettingsPart.protectRestrictFormatting(autoFormatOverride, styleLockTheme, styleLockQFSet, password, hashAlgo);
// app.xml DocSecurity
if (pkg.getDocPropsExtendedPart()==null)
{
pkg.addDocPropsExtendedPart();
}
this.setDocSecurity(0); // same as Word 2013
}
private static class VisitorRemovePFormatting extends TraversalUtilVisitor<P> {
StyleDefinitionsPart sdp;
List<String> allowedStyleNames;
VisitorRemovePFormatting(StyleDefinitionsPart sdp, List<String> allowedStyleNames) {
this.sdp = sdp;
this.allowedStyleNames = allowedStyleNames;
}
@Override
public void apply(P p, Object parent, List<Object> siblings) {
if (p.getPPr()!=null && p.getPPr().getPStyle()!=null &&
!allowedStyleNames.contains(sdp.getNameForStyleID(p.getPPr().getPStyle().getVal()))) {
p.getPPr().setPStyle(null);
}
}
}
private static class VisitorRemoveRFormatting extends TraversalUtilVisitor<R> {
StyleDefinitionsPart sdp;
List<String> allowedStyleNames;
VisitorRemoveRFormatting(StyleDefinitionsPart sdp, List<String> allowedStyleNames) {
this.sdp = sdp;
this.allowedStyleNames = allowedStyleNames;
}
@Override
public void apply(R r, Object parent, List<Object> siblings) {
if (r.getRPr()!=null && r.getRPr().getRStyle()!=null &&
!allowedStyleNames.contains(sdp.getNameForStyleID(r.getRPr().getRStyle().getVal()))) {
r.getRPr().setRStyle(null);
}
}
}
private static class VisitorRemoveTableFormatting extends TraversalUtilVisitor<Tbl> {
StyleDefinitionsPart sdp;
List<String> allowedStyleNames;
VisitorRemoveTableFormatting(StyleDefinitionsPart sdp, List<String> allowedStyleNames) {
this.sdp = sdp;
this.allowedStyleNames = allowedStyleNames;
}
@Override
public void apply(Tbl table, Object parent, List<Object> siblings) {
if (table.getTblPr()!=null && table.getTblPr().getTblStyle()!=null &&
!allowedStyleNames.contains(sdp.getNameForStyleID(table.getTblPr().getTblStyle().getVal()))) {
table.getTblPr().setTblStyle(null);
}
}
}
/**
* Verifies the documentProtection tag inside settings.xml file
* if the protection is enforced (w:enforcement="1")
* and if the kind of protection equals to passed (STDocProtect.Enum editValue)
*
* @return true if documentProtection is enforced with option readOnly
* @since 3.3.0
*/
public boolean isRestrictEditingWith(STDocProtect editValue) {
DocumentSettingsPart documentSettingsPart = getPkg().getMainDocumentPart().getDocumentSettingsPart();
if (documentSettingsPart == null) {
return false;
} else {
return isRestrictEditingWith( editValue);
}
}
/**
* Enforces the protection with the option specified by passed editValue.<br/>
* <br/>
* In the documentProtection tag inside settings.xml file <br/>
* it sets the value of enforcement to "1" (w:enforcement="1") <br/>
* and the value of edit to the passed editValue (w:edit="[passed editValue]")<br/>
* <br/>
* sample snippet from settings.xml
* <pre>
* <w:settings ... >
* <w:documentProtection w:edit="[passed editValue]" w:enforcement="1"/>
* </pre>
* @since 3.3.0
*/
public void restrictEditing(org.docx4j.wml.STDocProtect editValue) {
restrictEditing(editValue, null, null);
}
/**
* Enforces the protection with the option specified by passed editValue and password,
* using rsaFull (sha1) (like Word 2010).
*
* WARNING: this functionality may give a false sense of security, since it only affects
* the behaviour of Word's user interface. A mischevious user could still edit the document
* in some other program, and subsequent users would *not* be warned it has been tampered with.
*
* @param editValue the protection type
* @param password the plaintext password, if null no password will be applied
* @param hashAlgo the hash algorithm - only md2, m5, sha1, sha256, sha384 and sha512 are supported.
* if null, it will default default to sha512 (like Word 2013)
* @since 3.3.0
*/
public void restrictEditing(org.docx4j.wml.STDocProtect editValue,
String password) {
restrictEditing(editValue, password, HashAlgorithm.sha512);
/* Word 2013
*
w:cryptProviderType="rsaAES" w:cryptAlgorithmClass="hash" w:cryptAlgorithmType="typeAny"
w:cryptAlgorithmSid="14" w:cryptSpinCount="100000"
corresponds to sha512
case sha256:
providerType = STCryptProv.RSA_AES;
sid = 12;
break;
case sha384:
providerType = STCryptProv.RSA_AES;
sid = 13;
break;
case sha512:
providerType = STCryptProv.RSA_AES;
sid = 14;
*/
}
/**
* Enforces the protection with the option specified by passed editValue, password, and
* HashAlgorithm for the password.
*
* @param editValue the protection type
* @param password the plaintext password, if null no password will be applied
* @param hashAlgo the hash algorithm - only md2, m5, sha1, sha256, sha384 and sha512 are supported.
* if null, it will default default to sha512 (like Word 2013)
* @since 3.3.0
*/
public void restrictEditing(org.docx4j.wml.STDocProtect editValue,
String password, HashAlgorithm hashAlgo) {
DocumentSettingsPart documentSettingsPart = null;
try {
documentSettingsPart = getPkg().getMainDocumentPart().getDocumentSettingsPart(true);
} catch (InvalidFormatException e) {
log.warn(e.getMessage(), e);
}
documentSettingsPart.protectRestrictEditing(editValue, password, hashAlgo);
// app.xml DocSecurity
if (pkg.getDocPropsExtendedPart()==null)
{
pkg.addDocPropsExtendedPart();
}
// same as Word 2013:
if (editValue==STDocProtect.COMMENTS) {
this.setDocSecurity(8); // verified
} else if (editValue==STDocProtect.FORMS) {
this.setDocSecurity(0); // I think
} else if (editValue==STDocProtect.NONE) {
this.setDocSecurity(0); // I guess
} else if (editValue==STDocProtect.READ_ONLY) {
this.setDocSecurity(8); // verified
} else if (editValue==STDocProtect.TRACKED_CHANGES) {
this.setDocSecurity(0);
}
}
/**
* Validates the existing password
*
* @param password
* @return true, only if password was set and equals, false otherwise
* @since 3.3.0
*/
public boolean validateProtectionPassword(String password) {
DocumentSettingsPart documentSettingsPart = getPkg().getMainDocumentPart().getDocumentSettingsPart();
if (documentSettingsPart == null) {
return false;
} else {
return documentSettingsPart.validateProtectionPassword(password);
}
}
/**
* Removes protection enforcement.<br/>
* In the documentProtection tag inside settings.xml file <br/>
* it sets the value of enforcement to "0" (w:enforcement="0") <br/>
* @since 3.3.0
*/
public void removeEnforcement() {
DocumentSettingsPart documentSettingsPart = getPkg().getMainDocumentPart().getDocumentSettingsPart();
if (documentSettingsPart == null) {
return;
} else {
documentSettingsPart.removeEnforcement();
}
}
}