Add support for workbook protection (#1431)

pull/2/head
Gin 2 years ago committed by GitHub
parent 6a5ee811ba
commit 0c76766c2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -47,6 +47,7 @@ var (
packageEncryptionChunkSize = 4096
packageOffset = 8 // First 8 bytes are the size of the stream
sheetProtectionSpinCount = 1e5
workbookProtectionSpinCount = 1e5
)
// Encryption specifies the encryption structure, streams, and storages are

@ -230,4 +230,10 @@ var (
// ErrSheetNameLength defined the error message on receiving the sheet
// name length exceeds the limit.
ErrSheetNameLength = fmt.Errorf("the sheet name length exceeds the %d characters limit", MaxSheetNameLength)
// ErrUnprotectWorkbook defined the error message on workbook has set no
// protection.
ErrUnprotectWorkbook = errors.New("workbook has set no protect")
// ErrUnprotectWorkbookPassword defined the error message on remove workbook
// protection with password verification failed.
ErrUnprotectWorkbookPassword = errors.New("workbook protect password not match")
)

@ -1329,6 +1329,61 @@ func TestUnprotectSheet(t *testing.T) {
assert.EqualError(t, f.UnprotectSheet(sheetName, "wrongPassword"), "illegal base64 data at input byte 8")
}
func TestProtectWorkbook(t *testing.T) {
f := NewFile()
assert.NoError(t, f.ProtectWorkbook(nil))
// Test protect workbook with default hash algorithm
assert.NoError(t, f.ProtectWorkbook(&WorkbookProtectionOptions{
Password: "password",
LockStructure: true,
}))
wb, err := f.workbookReader()
assert.NoError(t, err)
assert.Equal(t, "SHA-512", wb.WorkbookProtection.WorkbookAlgorithmName)
assert.Equal(t, 24, len(wb.WorkbookProtection.WorkbookSaltValue))
assert.Equal(t, 88, len(wb.WorkbookProtection.WorkbookHashValue))
assert.Equal(t, int(workbookProtectionSpinCount), wb.WorkbookProtection.WorkbookSpinCount)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestProtectWorkbook.xlsx")))
// Test protect workbook with password exceeds the limit length
assert.EqualError(t, f.ProtectWorkbook(&WorkbookProtectionOptions{
AlgorithmName: "MD4",
Password: strings.Repeat("s", MaxFieldLength+1),
}), ErrPasswordLengthInvalid.Error())
// Test protect workbook with unsupported hash algorithm
assert.EqualError(t, f.ProtectWorkbook(&WorkbookProtectionOptions{
AlgorithmName: "RIPEMD-160",
Password: "password",
}), ErrUnsupportedHashAlgorithm.Error())
}
func TestUnprotectWorkbook(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, f.UnprotectWorkbook())
assert.EqualError(t, f.UnprotectWorkbook("password"), ErrUnprotectWorkbook.Error())
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestUnprotectWorkbook.xlsx")))
assert.NoError(t, f.Close())
f = NewFile()
assert.NoError(t, f.ProtectWorkbook(&WorkbookProtectionOptions{Password: "password"}))
// Test remove workbook protection with an incorrect password
assert.EqualError(t, f.UnprotectWorkbook("wrongPassword"), ErrUnprotectWorkbookPassword.Error())
// Test remove workbook protection with password verification
assert.NoError(t, f.UnprotectWorkbook("password"))
// Test with invalid salt value
assert.NoError(t, f.ProtectWorkbook(&WorkbookProtectionOptions{
AlgorithmName: "SHA-512",
Password: "password",
}))
wb, err := f.workbookReader()
assert.NoError(t, err)
wb.WorkbookProtection.WorkbookSaltValue = "YWJjZA====="
assert.EqualError(t, f.UnprotectWorkbook("wrongPassword"), "illegal base64 data at input byte 8")
}
func TestSetDefaultTimeStyle(t *testing.T) {
f := NewFile()
// Test set default time style on not exists worksheet.

@ -59,6 +59,67 @@ func (f *File) GetWorkbookProps() (WorkbookPropsOptions, error) {
return opts, err
}
// ProtectWorkbook provides a function to prevent other users from accidentally or
// deliberately changing, moving, or deleting data in a workbook.
func (f *File) ProtectWorkbook(opts *WorkbookProtectionOptions) error {
wb, err := f.workbookReader()
if err != nil {
return err
}
if wb.WorkbookProtection == nil {
wb.WorkbookProtection = new(xlsxWorkbookProtection)
}
if opts == nil {
opts = &WorkbookProtectionOptions{}
}
wb.WorkbookProtection = &xlsxWorkbookProtection{
LockStructure: opts.LockStructure,
LockWindows: opts.LockWindows,
}
if opts.Password != "" {
if opts.AlgorithmName == "" {
opts.AlgorithmName = "SHA-512"
}
hashValue, saltValue, err := genISOPasswdHash(opts.Password, opts.AlgorithmName, "", int(workbookProtectionSpinCount))
if err != nil {
return err
}
wb.WorkbookProtection.WorkbookAlgorithmName = opts.AlgorithmName
wb.WorkbookProtection.WorkbookSaltValue = saltValue
wb.WorkbookProtection.WorkbookHashValue = hashValue
wb.WorkbookProtection.WorkbookSpinCount = int(workbookProtectionSpinCount)
}
return nil
}
// UnprotectWorkbook provides a function to remove protection for workbook,
// specified the second optional password parameter to remove workbook
// protection with password verification.
func (f *File) UnprotectWorkbook(password ...string) error {
wb, err := f.workbookReader()
if err != nil {
return err
}
// password verification
if len(password) > 0 {
if wb.WorkbookProtection == nil {
return ErrUnprotectWorkbook
}
if wb.WorkbookProtection.WorkbookAlgorithmName != "" {
// check with given salt value
hashValue, _, err := genISOPasswdHash(password[0], wb.WorkbookProtection.WorkbookAlgorithmName, wb.WorkbookProtection.WorkbookSaltValue, wb.WorkbookProtection.WorkbookSpinCount)
if err != nil {
return err
}
if wb.WorkbookProtection.WorkbookHashValue != hashValue {
return ErrUnprotectWorkbookPassword
}
}
}
wb.WorkbookProtection = nil
return err
}
// setWorkbook update workbook property of the spreadsheet. Maximum 31
// characters are allowed in sheet title.
func (f *File) setWorkbook(name string, sheetID, rid int) {

@ -320,3 +320,11 @@ type WorkbookPropsOptions struct {
FilterPrivacy *bool `json:"filter_privacy,omitempty"`
CodeName *string `json:"code_name,omitempty"`
}
// WorkbookProtectionOptions directly maps the settings of workbook protection.
type WorkbookProtectionOptions struct {
AlgorithmName string `json:"algorithmName,omitempty"`
Password string `json:"password,omitempty"`
LockStructure bool `json:"lockStructure,omitempty"`
LockWindows bool `json:"lockWindows,omitempty"`
}

Loading…
Cancel
Save