| // Copyright 2014 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.google.enterprise.adaptor.fs; |
| |
| import static org.junit.Assert.*; |
| |
| import com.google.common.base.Preconditions; |
| import com.google.enterprise.adaptor.fs.WinApi.Netapi32Ex; |
| import com.google.enterprise.adaptor.fs.WinApi.Shlwapi; |
| import com.google.enterprise.adaptor.fs.WindowsAclFileAttributeViews.Mpr; |
| |
| import org.junit.*; |
| import org.junit.rules.TemporaryFolder; |
| |
| import com.sun.jna.Memory; |
| import com.sun.jna.Pointer; |
| import com.sun.jna.Structure; |
| import com.sun.jna.platform.win32.Advapi32; |
| import com.sun.jna.platform.win32.Advapi32Util.Account; |
| import com.sun.jna.platform.win32.Kernel32; |
| import com.sun.jna.platform.win32.Win32Exception; |
| import com.sun.jna.platform.win32.WinError; |
| import com.sun.jna.platform.win32.WinNT; |
| import com.sun.jna.platform.win32.WinNT.SID_NAME_USE; |
| |
| import java.io.IOException; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.List; |
| |
| /** |
| * Base class that provides mechanisms for faking JNA ACL access. |
| * Tests that wish to fake JNA calls to access ACLs should extend this. |
| */ |
| public class TestWindowsAclViews { |
| |
| // Store the SIDs in a map to avoid serializing and deserializing them. |
| static HashMap<Long, AccountSid> sidMap = new HashMap<Long, AccountSid>(); |
| |
| @Rule |
| public final TemporaryFolder temp = new TemporaryFolder(); |
| |
| protected final Path newTempDir(String name) throws IOException { |
| return temp.newFolder(name).toPath().toRealPath(); |
| } |
| |
| protected final Path newTempFile(String name) throws IOException { |
| return temp.newFile(name).toPath().toRealPath(); |
| } |
| |
| protected final Path newTempFile(Path parent, String name) |
| throws IOException { |
| Preconditions.checkArgument(parent.startsWith(getTempRoot())); |
| return Files.createFile(parent.resolve(name)); |
| } |
| |
| protected final Path getTempRoot() throws IOException { |
| return temp.getRoot().getCanonicalFile().toPath(); |
| } |
| |
| /** |
| * Serializes an WinNT ACEs into a byte buffer representing a DACL. |
| * This byte buffer is suitable for reading back via |
| * <code>new WinNT.SECURITY_DESCRIPTOR_RELATIVE(Memory)</code>. |
| */ |
| protected static final byte[] buildDaclMemory( |
| WinNT.ACCESS_ACEStructure... aces) throws Exception { |
| WinNT.ACL acl = new WinNT.ACL(); |
| WinNT.SECURITY_DESCRIPTOR_RELATIVE securityDescriptor = |
| new WinNT.SECURITY_DESCRIPTOR_RELATIVE(); |
| int totalSize = securityDescriptor.size() + acl.size(); |
| for (WinNT.ACCESS_ACEStructure ace : aces) { |
| totalSize += ace.AceSize; |
| } |
| |
| // Serialize the structures into a buffer. |
| final byte[] buffer = new byte[totalSize]; |
| int offset = 0; |
| // The start of the ACL follows the securityDescriptor in memory. |
| securityDescriptor.Dacl = securityDescriptor.size(); |
| securityDescriptor.write(); |
| securityDescriptor.getPointer().read(0, buffer, offset, |
| securityDescriptor.size()); |
| offset += securityDescriptor.size(); |
| acl.AceCount = (short) aces.length; |
| acl.write(); |
| acl.getPointer().read(0, buffer, offset, acl.size()); |
| offset += acl.size(); |
| for (WinNT.ACCESS_ACEStructure ace : aces) { |
| ace.write(); |
| ace.getPointer().read(0, buffer, offset, ace.AceSize); |
| offset += ace.AceSize; |
| } |
| return buffer; |
| } |
| |
| /** |
| * A convenient Ace Builder. Only AccountSid SIDs are supported. |
| */ |
| public static class AceBuilder { |
| private byte type; |
| private byte flags; |
| private int perms; |
| private AccountSid sid; |
| |
| public AceBuilder setType(byte type) { |
| this.type = type; |
| return this; |
| } |
| |
| public AceBuilder setFlags(byte... flags) { |
| for (byte flag : flags) { |
| this.flags |= flag; |
| } |
| return this; |
| } |
| |
| public AceBuilder setPerms(int... perms) { |
| for (int perm : perms) { |
| this.perms |= perm; |
| } |
| return this; |
| } |
| |
| public AceBuilder setSid(AccountSid sid) { |
| this.sid = sid; |
| return this; |
| } |
| |
| public WinNT.ACCESS_ACEStructure build() { |
| // Because ACCESS_ACEStructure does not allow me to set the SID |
| // directly, I must create a serialized ACE containing a Pointer |
| // to my AccountSid, then create a new ACE from that memory. |
| WinNT.ACCESS_ACEStructure ace = new Ace(); |
| ace.AceType = type; |
| ace.AceFlags = flags; |
| ace.Mask = perms; |
| ace.AceSize = (short)(ace.size() + Pointer.SIZE); |
| ace.write(); |
| byte[] buffer = new byte[ace.AceSize]; |
| ace.getPointer().read(0, buffer, 0, ace.size()); |
| Memory memory = new Memory(buffer.length); |
| memory.write(0, buffer, 0, ace.size()); |
| sid.write(); |
| // See ACCESS_ACEStructure(Pointer p) constructor for mystery offsets. |
| memory.setPointer(4 + 4, sid.getPointer()); |
| sidMap.put(Pointer.nativeValue(sid.getPointer()), sid); |
| ace = new Ace(memory); |
| assertEquals(ace.getSID().sid, sid.getPointer()); |
| return ace; |
| } |
| } |
| |
| /** |
| * A subclass of WinNT.ACCESS_ACEStructure that avoids using |
| * Advapi32Util to get the string SID. |
| */ |
| public static class Ace extends WinNT.ACCESS_ACEStructure { |
| public Ace() { |
| } |
| |
| public Ace(Pointer p) { |
| super(p); |
| } |
| |
| @Override |
| public String getSidString() { |
| return new AccountSid(getSID().sid).toString(); |
| } |
| } |
| |
| /** |
| * A SID structure that encapsulates the <code>Advapi32Util.Account</code> |
| * information, avoiding network lookups to in <code>getAccountBySid</code>. |
| */ |
| public static class AccountSid extends Structure { |
| |
| public static AccountSid user(String name, String domain) { |
| return new AccountSid(SID_NAME_USE.SidTypeUser, name, domain); |
| } |
| |
| public static AccountSid group(String name, String domain) { |
| return new AccountSid(SID_NAME_USE.SidTypeGroup, name, domain); |
| } |
| |
| @Override |
| protected List getFieldOrder() { |
| return Arrays.asList(new String[] { "type", "name", "domain" }); |
| } |
| |
| public int type; |
| public String name; |
| public String domain; |
| |
| public AccountSid() { |
| } |
| |
| public AccountSid(Pointer p) { |
| super(p); |
| read(); |
| } |
| |
| public AccountSid(int type, String name, String domain) { |
| this.type = type; |
| this.name = name; |
| this.domain = domain; |
| } |
| |
| public Account getAccount() throws Win32Exception { |
| if (name == null && domain == null) { |
| throw new Win32Exception(WinError.ERROR_NONE_MAPPED); |
| } else { |
| Account account = new Account(); |
| account.accountType = type; |
| account.name = name; |
| account.domain = domain; |
| return account; |
| } |
| } |
| |
| @Override |
| public String toString() { |
| if (name == null && domain == null) { |
| return "null"; |
| } else { |
| return (domain == null) ? name : domain + "\\" + name; |
| } |
| } |
| } |
| |
| /** |
| * An subclass of {@link WindowsAclFileAttributeViews} that avoids making |
| * actual Windows API calls by using faked JNA implementations. |
| */ |
| public static class TestAclFileAttributeViews |
| extends WindowsAclFileAttributeViews { |
| |
| public TestAclFileAttributeViews() { |
| super(null, null, null, null, null); |
| } |
| |
| public TestAclFileAttributeViews(Advapi32 advapi32, Kernel32 kernel32, |
| Mpr mpr, Netapi32Ex netapi32, Shlwapi shlwapi) { |
| super(advapi32, kernel32, mpr, netapi32, shlwapi); |
| } |
| |
| @Override |
| Account getAccountBySid(WinNT.PSID sid) throws Win32Exception { |
| return sidMap.get(Pointer.nativeValue(sid.sid)).getAccount(); |
| } |
| } |
| } |