diff --git a/ClosedXML/Excel/XLWorkbook.cs b/ClosedXML/Excel/XLWorkbook.cs index e946369..9e86a67 100644 --- a/ClosedXML/Excel/XLWorkbook.cs +++ b/ClosedXML/Excel/XLWorkbook.cs @@ -1,14 +1,14 @@ +using ClosedXML.Excel.CalcEngine; +using ClosedXML.Extensions; +using DocumentFormat.OpenXml; using System; using System.Collections.Generic; +using System.Data; using System.IO; -using System.Security.AccessControl; -using ClosedXML.Excel.CalcEngine; -using DocumentFormat.OpenXml; +using System.Linq; namespace ClosedXML.Excel { - using System.Linq; - using System.Data; public enum XLEventTracking { Enabled, Disabled } public enum XLCalculateMode @@ -864,12 +864,49 @@ public XLWorkbook SetLockStructure(Boolean value) { LockStructure = value; return this; } public Boolean LockWindows { get; set; } public XLWorkbook SetLockWindows(Boolean value) { LockWindows = value; return this; } + internal HexBinaryValue LockPassword { get; set; } + public Boolean IsPasswordProtected { get { return LockPassword != null; } } + + public void Protect(Boolean lockStructure, Boolean lockWindows, String workbookPassword) + { + if (IsPasswordProtected && workbookPassword == null) + throw new InvalidOperationException("The workbook is password protected"); + var hashPassword = workbookPassword.HashPassword(); + if (IsPasswordProtected && LockPassword != hashPassword) + throw new ArgumentException("Invalid password"); + + if (IsPasswordProtected && (lockStructure || lockWindows)) + throw new InvalidOperationException("The workbook is already protected"); + + if (IsPasswordProtected && hashPassword != null && !lockStructure && !lockWindows) + { + // Workbook currently protected, but we're unsetting the 2 flags + // Hence unprotect workbook using password. + LockPassword = null; + } + + + if (!IsPasswordProtected && hashPassword != null && (lockStructure || lockWindows)) + { + //Protect workbook using password. + LockPassword = hashPassword; + } + + LockStructure = lockStructure; + LockWindows = lockWindows; + } + public void Protect() { Protect(true); } + public void Protect(string workbookPassword) + { + Protect(true, false, workbookPassword); + } + public void Protect(Boolean lockStructure) { Protect(lockStructure, false); @@ -877,8 +914,17 @@ public void Protect(Boolean lockStructure, Boolean lockWindows) { - LockStructure = lockStructure; - LockWindows = LockWindows; + Protect(lockStructure, lockWindows, null); + } + + public void Unprotect() + { + Protect(false, false); + } + + public void Unprotect(string workbookPassword) + { + Protect(false, false, workbookPassword); } } -} +} \ No newline at end of file diff --git a/ClosedXML/Excel/XLWorkbook_Load.cs b/ClosedXML/Excel/XLWorkbook_Load.cs index cf84f81..a23ab4e 100644 --- a/ClosedXML/Excel/XLWorkbook_Load.cs +++ b/ClosedXML/Excel/XLWorkbook_Load.cs @@ -97,6 +97,8 @@ LockStructure = wbProtection.LockStructure.Value; if (wbProtection.LockWindows != null) LockWindows = wbProtection.LockWindows.Value; + if (wbProtection.WorkbookPassword != null) + LockPassword = wbProtection.WorkbookPassword.Value; } var calculationProperties = dSpreadsheet.WorkbookPart.Workbook.CalculationProperties; @@ -2397,4 +2399,4 @@ return false; } } -} +} \ No newline at end of file diff --git a/ClosedXML/Excel/XLWorkbook_Save.cs b/ClosedXML/Excel/XLWorkbook_Save.cs index 20a9088..19ba801 100644 --- a/ClosedXML/Excel/XLWorkbook_Save.cs +++ b/ClosedXML/Excel/XLWorkbook_Save.cs @@ -572,6 +572,8 @@ #endregion WorkbookProperties + #region WorkbookProtection + if (LockStructure || LockWindows) { if (workbook.WorkbookProtection == null) @@ -579,11 +581,16 @@ workbook.WorkbookProtection.LockStructure = LockStructure; workbook.WorkbookProtection.LockWindows = LockWindows; + + if (LockPassword != null) + workbook.WorkbookProtection.WorkbookPassword = LockPassword; } else { workbook.WorkbookProtection = null; } + + #endregion if (workbook.BookViews == null) workbook.BookViews = new BookViews(); @@ -5038,4 +5045,4 @@ #endregion GenerateWorksheetPartContent } -} +} \ No newline at end of file diff --git a/ClosedXML/Extensions/StringExtensions.cs b/ClosedXML/Extensions/StringExtensions.cs index a3b49ec..facf0a5 100644 --- a/ClosedXML/Extensions/StringExtensions.cs +++ b/ClosedXML/Extensions/StringExtensions.cs @@ -8,12 +8,31 @@ { internal static class StringExtensions { - internal static string WrapSheetNameInQuotesIfRequired(this string sheetName) + internal static String WrapSheetNameInQuotesIfRequired(this String sheetName) { if (sheetName.Contains(' ')) return "'" + sheetName + "'"; else return sheetName; } + + internal static String HashPassword(this String password) + { + if (password == null) return null; + + Int32 pLength = password.Length; + Int32 hash = 0; + if (pLength == 0) return String.Empty; + + for (Int32 i = pLength - 1; i >= 0; i--) + { + hash ^= password[i]; + hash = hash >> 14 & 0x01 | hash << 1 & 0x7fff; + } + hash ^= 0x8000 | 'N' << 8 | 'K'; + hash ^= pLength; + return hash.ToString("X"); + } + } } diff --git a/ClosedXML_Examples/ClosedXML_Examples.csproj b/ClosedXML_Examples/ClosedXML_Examples.csproj index aee3bcf..9f4871b 100644 --- a/ClosedXML_Examples/ClosedXML_Examples.csproj +++ b/ClosedXML_Examples/ClosedXML_Examples.csproj @@ -88,6 +88,7 @@ + diff --git a/ClosedXML_Examples/Misc/WorkbookProtection.cs b/ClosedXML_Examples/Misc/WorkbookProtection.cs new file mode 100644 index 0000000..0bd0122 --- /dev/null +++ b/ClosedXML_Examples/Misc/WorkbookProtection.cs @@ -0,0 +1,23 @@ +using System; +using ClosedXML.Excel; + +namespace ClosedXML_Examples.Misc +{ + public class WorkbookProtection : IXLExample + { + #region Methods + + // Public + public void Create(String filePath) + { + using (var wb = new XLWorkbook()) + { + var ws = wb.Worksheets.Add("Workbook Protection"); + wb.Protect(true, false, "Abc@123"); + wb.SaveAs(filePath); + } + } + + #endregion + } +} diff --git a/ClosedXML_Tests/ClosedXML_Tests.csproj b/ClosedXML_Tests/ClosedXML_Tests.csproj index 3f12c66..487ea3a 100644 --- a/ClosedXML_Tests/ClosedXML_Tests.csproj +++ b/ClosedXML_Tests/ClosedXML_Tests.csproj @@ -281,6 +281,7 @@ + diff --git a/ClosedXML_Tests/Examples/MiscTests.cs b/ClosedXML_Tests/Examples/MiscTests.cs index c1f97bf..4f14453 100644 --- a/ClosedXML_Tests/Examples/MiscTests.cs +++ b/ClosedXML_Tests/Examples/MiscTests.cs @@ -204,5 +204,11 @@ { TestHelper.RunTestExample(@"Misc\WorkbookProperties.xlsx"); } + + [Test] + public void WorkbookProtection() + { + TestHelper.RunTestExample(@"Misc\WorkbookProtection.xlsx"); + } } -} +} \ No newline at end of file diff --git a/ClosedXML_Tests/Excel/Misc/XLWorkbookTests.cs b/ClosedXML_Tests/Excel/Misc/XLWorkbookTests.cs index 48f5225..5a1e4a7 100644 --- a/ClosedXML_Tests/Excel/Misc/XLWorkbookTests.cs +++ b/ClosedXML_Tests/Excel/Misc/XLWorkbookTests.cs @@ -255,5 +255,81 @@ Assert.AreEqual("$A$1:$A$1", wsRanges.First().RangeAddress.ToStringFixed()); Assert.AreEqual("$A$3:$A$3", wsRanges.Last().RangeAddress.ToStringFixed()); } + + [Test] + public void WbProtect1() + { + using (var wb = new XLWorkbook()) + { + var ws = wb.Worksheets.Add("Sheet1"); + wb.Protect(); + Assert.IsTrue(wb.LockStructure); + Assert.IsFalse(wb.LockWindows); + Assert.IsFalse(wb.IsPasswordProtected); + } + } + + [Test] + public void WbProtect2() + { + using (var wb = new XLWorkbook()) + { + var ws = wb.Worksheets.Add("Sheet1"); + wb.Protect(true, false); + Assert.IsTrue(wb.LockStructure); + Assert.IsFalse(wb.LockWindows); + Assert.IsFalse(wb.IsPasswordProtected); + } + } + + [Test] + public void WbProtect3() + { + using (var wb = new XLWorkbook()) + { + var ws = wb.Worksheets.Add("Sheet1"); + wb.Protect("Abc@123"); + Assert.IsTrue(wb.LockStructure); + Assert.IsFalse(wb.LockWindows); + Assert.IsTrue(wb.IsPasswordProtected); + Assert.Throws(typeof(InvalidOperationException), delegate { wb.Protect(); }); + Assert.Throws(typeof(InvalidOperationException), delegate { wb.Unprotect(); }); + Assert.Throws(typeof(ArgumentException), delegate { wb.Unprotect("Cde@345"); }); + } + } + + [Test] + public void WbProtect4() + { + using (var wb = new XLWorkbook()) + { + var ws = wb.Worksheets.Add("Sheet1"); + wb.Protect(); + Assert.IsTrue(wb.LockStructure); + Assert.IsFalse(wb.LockWindows); + Assert.IsFalse(wb.IsPasswordProtected); + wb.Protect("Abc@123"); + Assert.IsTrue(wb.LockStructure); + Assert.IsFalse(wb.LockWindows); + Assert.IsTrue(wb.IsPasswordProtected); + } + } + + [Test] + public void WbProtect5() + { + using (var wb = new XLWorkbook()) + { + var ws = wb.Worksheets.Add("Sheet1"); + wb.Protect(true, false, "Abc@123"); + Assert.IsTrue(wb.LockStructure); + Assert.IsFalse(wb.LockWindows); + Assert.IsTrue(wb.IsPasswordProtected); + wb.Unprotect("Abc@123"); + Assert.IsFalse(wb.LockStructure); + Assert.IsFalse(wb.LockWindows); + Assert.IsFalse(wb.IsPasswordProtected); + } + } } -} \ No newline at end of file +} diff --git a/ClosedXML_Tests/Resource/Examples/Misc/WorkbookProtection.xlsx b/ClosedXML_Tests/Resource/Examples/Misc/WorkbookProtection.xlsx new file mode 100644 index 0000000..2e7c839 --- /dev/null +++ b/ClosedXML_Tests/Resource/Examples/Misc/WorkbookProtection.xlsx Binary files differ