diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlFilter.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlFilter.java index 559c593..c447445 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlFilter.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlFilter.java @@ -19,14 +19,17 @@ package org.apache.hadoop.hbase.security.access; import java.io.IOException; +import java.net.InetAddress; +import java.util.List; import java.util.Map; -import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.Cell; import org.apache.hadoop.hbase.CellUtil; import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.exceptions.DeserializationException; import org.apache.hadoop.hbase.filter.FilterBase; +import org.apache.hadoop.hbase.ipc.RpcServer; import org.apache.hadoop.hbase.security.User; import org.apache.hadoop.hbase.util.ByteRange; import org.apache.hadoop.hbase.util.SimpleMutableByteRange; @@ -62,6 +65,7 @@ private boolean isSystemTable; private Strategy strategy; private Map cfVsMaxVersions; + private List superusers; private int familyMaxVersions; private int currentVersions; private ByteRange prevFam; @@ -74,7 +78,7 @@ } AccessControlFilter(TableAuthManager mgr, User ugi, TableName tableName, - Strategy strategy, Map cfVsMaxVersions) { + Strategy strategy, Map cfVsMaxVersions, List superusers) { authManager = mgr; table = tableName; user = ugi; @@ -83,6 +87,7 @@ this.cfVsMaxVersions = cfVsMaxVersions; this.prevFam = new SimpleMutableByteRange(); this.prevQual = new SimpleMutableByteRange(); + this.superusers = superusers; } @Override @@ -122,15 +127,16 @@ switch (strategy) { // Filter only by checking the table or CF permissions case CHECK_TABLE_AND_CF_ONLY: { - if (authManager.authorize(user, table, family, qualifier, Permission.Action.READ)) { + if (authManager.authorize(user, table, family, qualifier, Permission.Action.READ, getRemoteAddr())) { return ReturnCode.INCLUDE; } } break; // Cell permissions can override table or CF permissions case CHECK_CELL_DEFAULT: { - if (authManager.authorize(user, table, family, qualifier, Permission.Action.READ) || - authManager.authorize(user, table, cell, Permission.Action.READ)) { + String remoteAddr = getRemoteAddr(); + if (authManager.authorize(user, table, family, qualifier, Permission.Action.READ, remoteAddr) || + authManager.authorize(user, table, cell, Permission.Action.READ, remoteAddr)) { return ReturnCode.INCLUDE; } } @@ -142,7 +148,19 @@ return ReturnCode.SKIP; } - @Override + private String getRemoteAddr() { + String remoteAddr = null; + if (superusers.contains(user.getShortName())) { + return remoteAddr; + } + InetAddress address = RpcServer.getRemoteIp(); + if(address != null){ + remoteAddr = address.getHostAddress(); + } + return remoteAddr; + } + +@Override public void reset() throws IOException { this.prevFam.unset(); this.prevQual.unset(); diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlLists.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlLists.java index f0723c2..a31fe27 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlLists.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessControlLists.java @@ -652,7 +652,7 @@ return Arrays.copyOfRange(namespace, 1, namespace.length); } - public static List getCellPermissionsForUser(User user, Cell cell) + public static List getCellPermissionsForUser(User user, Cell cell, String userAddr) throws IOException { // Save an object allocation where we can if (cell.getTagsLength() == 0) { @@ -677,7 +677,7 @@ ListMultimap kvPerms = ProtobufUtil.toUsersAndPermissions(builder.build()); // Are there permissions for this user? - List userPerms = kvPerms.get(user.getShortName()); + List userPerms = kvPerms.get(userAddr); if (userPerms != null) { results.addAll(userPerms); } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java index fb19a96..eb11e05 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java @@ -24,6 +24,7 @@ import java.net.InetAddress; import java.security.PrivilegedExceptionAction; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; @@ -193,6 +194,7 @@ new MapMaker().weakKeys().makeMap(); private Map> tableAcls; + private List superusers; /** Provider for mapping principal names to Users */ private UserProvider userProvider; @@ -304,6 +306,7 @@ Map> families) { HRegionInfo hri = e.getRegion().getRegionInfo(); TableName tableName = hri.getTable(); + String remoteAddr = getRemoteAddr(user); // 1. All users need read access to hbase:meta table. // this is a very common operation, so deal with it quickly. @@ -320,7 +323,7 @@ } // 2. check for the table-level, if successful we can short-circuit - if (authManager.authorize(user, tableName, (byte[])null, permRequest)) { + if (authManager.authorize(user, tableName, (byte[])null, permRequest, remoteAddr)) { return AuthResult.allow(request, "Table permission granted", user, permRequest, tableName, families); } @@ -331,7 +334,7 @@ for (Map.Entry> family : families.entrySet()) { // a) check for family level access if (authManager.authorize(user, tableName, family.getKey(), - permRequest)) { + permRequest, remoteAddr)) { continue; // family-level permission overrides per-qualifier } @@ -342,7 +345,7 @@ Set familySet = (Set)family.getValue(); for (byte[] qualifier : familySet) { if (!authManager.authorize(user, tableName, family.getKey(), - qualifier, permRequest)) { + qualifier, permRequest, remoteAddr)) { return AuthResult.deny(request, "Failed qualifier check", user, permRequest, tableName, makeFamilyMap(family.getKey(), qualifier)); } @@ -351,7 +354,7 @@ List kvList = (List)family.getValue(); for (KeyValue kv : kvList) { if (!authManager.authorize(user, tableName, family.getKey(), - CellUtil.cloneQualifier(kv), permRequest)) { + CellUtil.cloneQualifier(kv), permRequest, remoteAddr)) { return AuthResult.deny(request, "Failed qualifier check", user, permRequest, tableName, makeFamilyMap(family.getKey(), CellUtil.cloneQualifier(kv))); } @@ -372,6 +375,18 @@ // 4. no families to check and table level access failed return AuthResult.deny(request, "No families to check and table permission failed", user, permRequest, tableName, families); + } + + private String getRemoteAddr(User user) { + String remoteAddr = null; + if (superusers.contains(user.getShortName())) { + return remoteAddr; + } + InetAddress address = RpcServer.getRemoteIp(); + if(address != null){ + remoteAddr = address.getHostAddress(); + } + return remoteAddr; } /** @@ -436,9 +451,10 @@ byte[] qualifier, Action... permissions) throws IOException { User user = getActiveUser(); AuthResult result = null; + String remoteAddr = getRemoteAddr(user); for (Action permission : permissions) { - if (authManager.authorize(user, tableName, family, qualifier, permission)) { + if (authManager.authorize(user, tableName, family, qualifier, permission, remoteAddr)) { result = AuthResult.allow(request, "Table permission granted", user, permission, tableName, family, qualifier); break; @@ -467,9 +483,10 @@ byte[] qualifier, Action... permissions) throws IOException { User user = getActiveUser(); AuthResult result = null; + String remoteAddr = getRemoteAddr(user); for (Action permission : permissions) { - if (authManager.authorize(user, tableName, null, null, permission)) { + if (authManager.authorize(user, tableName, null, null, permission, remoteAddr)) { result = AuthResult.allow(request, "Table permission granted", user, permission, tableName, null, null); result.getParams().setFamily(family).setQualifier(qualifier); @@ -499,9 +516,10 @@ Action... permissions) throws IOException { User user = getActiveUser(); AuthResult result = null; + String remoteAddr = getRemoteAddr(user); for (Action permission : permissions) { - if (authManager.hasAccess(user, tableName, permission)) { + if (authManager.hasAccess(user, tableName, permission, remoteAddr)) { result = AuthResult.allow(request, "Table permission granted", user, permission, tableName, null, null); break; @@ -539,7 +557,7 @@ Map> familyMap) throws IOException { User user = getActiveUser(); AuthResult result = null; - if (authManager.authorize(user, perm)) { + if (authManager.authorize(user, perm, getRemoteAddr(user))) { result = AuthResult.allow(request, "Global check allowed", user, perm, tableName, familyMap); result.getParams().setTableName(tableName).setFamilies(familyMap); logResult(result); @@ -566,7 +584,7 @@ String namespace) throws IOException { User user = getActiveUser(); AuthResult authResult = null; - if (authManager.authorize(user, perm)) { + if (authManager.authorize(user, perm, getRemoteAddr(user))) { authResult = AuthResult.allow(request, "Global check allowed", user, perm, null); authResult.getParams().setNamespace(namespace); logResult(authResult); @@ -593,7 +611,7 @@ AuthResult result = null; for (Action permission : permissions) { - if (authManager.authorize(user, namespace, permission)) { + if (authManager.authorize(user, namespace, permission, getRemoteAddr(user))) { result = AuthResult.allow(request, "Namespace permission granted", user, permission, namespace); break; @@ -622,7 +640,7 @@ AuthResult result = null; for (Action permission : permissions) { - if (authManager.authorize(user, namespace, permission)) { + if (authManager.authorize(user, namespace, permission, getRemoteAddr(user))) { result = AuthResult.allow(request, "Namespace permission granted", user, permission, namespace); result.getParams().setTableName(tableName).setFamilies(familyMap); @@ -664,13 +682,13 @@ if (family.getValue() != null && !family.getValue().isEmpty()) { for (byte[] qualifier : family.getValue()) { if (authManager.matchPermission(user, tableName, - family.getKey(), qualifier, perm)) { + family.getKey(), qualifier, perm, getRemoteAddr(user))) { return true; } } } else { if (authManager.matchPermission(user, tableName, family.getKey(), - perm)) { + perm, getRemoteAddr(user))) { return true; } } @@ -860,7 +878,7 @@ foundColumn = true; for (Action action: actions) { // Are there permissions for this user for the cell? - if (!authManager.authorize(user, getTableName(e), cell, action)) { + if (!authManager.authorize(user, getTableName(e), cell, action, getRemoteAddr(user))) { // We can stop if the cell ACL denies access return false; } @@ -936,6 +954,7 @@ conf.add(env.getConfiguration()); authorizationEnabled = isAuthorizationSupported(conf); + superusers = Arrays.asList(conf.getStrings("hbase.superuser")); if (!authorizationEnabled) { LOG.warn("The AccessController has been loaded with authorization checks disabled."); } @@ -1549,7 +1568,7 @@ if (authorizationEnabled) { Filter ourFilter = new AccessControlFilter(authManager, user, table, AccessControlFilter.Strategy.CHECK_TABLE_AND_CF_ONLY, - cfVsMaxVersions); + cfVsMaxVersions, superusers); // wrap any existing filter if (filter != null) { ourFilter = new FilterList(FilterList.Operator.MUST_PASS_ALL, @@ -1578,7 +1597,7 @@ // Only wrap the filter if we are enforcing authorizations if (authorizationEnabled) { Filter ourFilter = new AccessControlFilter(authManager, user, table, - AccessControlFilter.Strategy.CHECK_CELL_DEFAULT, cfVsMaxVersions); + AccessControlFilter.Strategy.CHECK_CELL_DEFAULT, cfVsMaxVersions, superusers); // wrap any existing filter if (filter != null) { ourFilter = new FilterList(FilterList.Operator.MUST_PASS_ALL, @@ -2401,7 +2420,7 @@ for (Action action : permission.getActions()) { AuthResult result; - if (authManager.authorize(user, action)) { + if (authManager.authorize(user, action, getRemoteAddr(user))) { result = AuthResult.allow("checkPermissions", "Global action allowed", user, action, null, null); } else { diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/TableAuthManager.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/TableAuthManager.java index f7479cf..93844b5 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/TableAuthManager.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/TableAuthManager.java @@ -316,12 +316,12 @@ * @param action * @return true if known and authorized, false otherwise */ - public boolean authorize(User user, Permission.Action action) { + public boolean authorize(User user, Permission.Action action, String remoteAddr) { if (user == null) { return false; } - if (authorize(globalCache.getUser(user.getShortName()), action)) { + if (authorize(globalCache.getUser(getUserAddr(user, remoteAddr)), action)) { return true; } @@ -334,6 +334,15 @@ } } return false; + } + + private String getUserAddr(User user, String remoteAddr) { + StringBuffer sb = new StringBuffer(); + sb.append(user.getShortName()); + if(remoteAddr != null){ + sb.append("@"+remoteAddr); + } + return sb.toString(); } private boolean authorize(List perms, @@ -368,9 +377,10 @@ /** * Authorize a user for a given KV. This is called from AccessControlFilter. */ - public boolean authorize(User user, TableName table, Cell cell, Permission.Action action) { + public boolean authorize(User user, TableName table, Cell cell, Permission.Action action, String remoteAddr) { try { - List perms = AccessControlLists.getCellPermissionsForUser(user, cell); + String userAddr = this.getUserAddr(user, remoteAddr); + List perms = AccessControlLists.getCellPermissionsForUser(user, cell, userAddr); if (LOG.isTraceEnabled()) { LOG.trace("Perms for user " + user.getShortName() + " in cell " + cell + ": " + (perms != null ? perms : "")); @@ -391,15 +401,15 @@ return false; } - public boolean authorize(User user, String namespace, Permission.Action action) { + public boolean authorize(User user, String namespace, Permission.Action action, String remoteAddr) { // Global authorizations supercede namespace level - if (authorize(user, action)) { + if (authorize(user, action, remoteAddr)) { return true; } // Check namespace permissions PermissionCache tablePerms = nsCache.get(namespace); if (tablePerms != null) { - List userPerms = tablePerms.getUser(user.getShortName()); + List userPerms = tablePerms.getUser(getUserAddr(user, remoteAddr)); if (authorize(userPerms, namespace, action)) { return true; } @@ -442,20 +452,20 @@ * @return true if known and authorized, false otherwise */ public boolean authorizeUser(User user, TableName table, byte[] family, - Permission.Action action) { - return authorizeUser(user, table, family, null, action); + Permission.Action action, String remoteAddr) { + return authorizeUser(user, table, family, null, action, remoteAddr); } public boolean authorizeUser(User user, TableName table, byte[] family, - byte[] qualifier, Permission.Action action) { + byte[] qualifier, Permission.Action action, String remoteAddr) { if (table == null) table = AccessControlLists.ACL_TABLE_NAME; // Global and namespace authorizations supercede table level - if (authorize(user, table.getNamespaceAsString(), action)) { + if (authorize(user, table.getNamespaceAsString(), action, remoteAddr)) { return true; } // Check table permissions - return authorize(getTablePermissions(table).getUser(user.getShortName()), table, family, - qualifier, action); + String key = getUserAddr(user, remoteAddr); + return authorize(getTablePermissions(table).getUser(key), table, family, qualifier, action); } /** @@ -467,14 +477,14 @@ * @param action * @return true if the user has access to the table, false otherwise */ - public boolean userHasAccess(User user, TableName table, Permission.Action action) { + public boolean userHasAccess(User user, TableName table, Permission.Action action, String remoteAddr) { if (table == null) table = AccessControlLists.ACL_TABLE_NAME; // Global and namespace authorizations supercede table level - if (authorize(user, table.getNamespaceAsString(), action)) { + if (authorize(user, table.getNamespaceAsString(), action, remoteAddr)) { return true; } // Check table permissions - return hasAccess(getTablePermissions(table).getUser(user.getShortName()), table, action); + return hasAccess(getTablePermissions(table).getUser(getUserAddr(user, remoteAddr)), table, action); } /** @@ -546,8 +556,8 @@ } public boolean authorize(User user, TableName table, byte[] family, - byte[] qualifier, Permission.Action action) { - if (authorizeUser(user, table, family, qualifier, action)) { + byte[] qualifier, Permission.Action action, String remoteAddr) { + if (authorizeUser(user, table, family, qualifier, action, remoteAddr)) { return true; } @@ -562,8 +572,8 @@ return false; } - public boolean hasAccess(User user, TableName table, Permission.Action action) { - if (userHasAccess(user, table, action)) { + public boolean hasAccess(User user, TableName table, Permission.Action action, String remoteAddr) { + if (userHasAccess(user, table, action, remoteAddr)) { return true; } @@ -579,8 +589,8 @@ } public boolean authorize(User user, TableName table, byte[] family, - Permission.Action action) { - return authorize(user, table, family, null, action); + Permission.Action action, String remoteAddr) { + return authorize(user, table, family, null, action, remoteAddr); } /** @@ -590,10 +600,10 @@ * authorize() on the same column family would return true. */ public boolean matchPermission(User user, - TableName table, byte[] family, Permission.Action action) { + TableName table, byte[] family, Permission.Action action, String remoteAddr) { PermissionCache tablePerms = tableCache.get(table); if (tablePerms != null) { - List userPerms = tablePerms.getUser(user.getShortName()); + List userPerms = tablePerms.getUser(getUserAddr(user, remoteAddr)); if (userPerms != null) { for (TablePermission p : userPerms) { if (p.matchesFamily(table, family, action)) { @@ -622,10 +632,10 @@ public boolean matchPermission(User user, TableName table, byte[] family, byte[] qualifier, - Permission.Action action) { + Permission.Action action, String remoteAddr) { PermissionCache tablePerms = tableCache.get(table); if (tablePerms != null) { - List userPerms = tablePerms.getUser(user.getShortName()); + List userPerms = tablePerms.getUser(getUserAddr(user, remoteAddr)); if (userPerms != null) { for (TablePermission p : userPerms) { if (p.matchesFamilyQualifier(table, family, qualifier, action)) { diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestTablePermissions.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestTablePermissions.java index f7def51..e8d7147 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestTablePermissions.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestTablePermissions.java @@ -436,13 +436,13 @@ TableAuthManager authManager = TableAuthManager.getOrCreate(ZKW, conf); // currently running user is the system user and should have global admin perms User currentUser = User.getCurrent(); - assertTrue(authManager.authorize(currentUser, Permission.Action.ADMIN)); + assertTrue(authManager.authorize(currentUser, Permission.Action.ADMIN, null)); for (int i=1; i<=50; i++) { AccessControlLists.addUserPermission(conf, new UserPermission(Bytes.toBytes("testauth"+i), Permission.Action.ADMIN, Permission.Action.READ, Permission.Action.WRITE)); // make sure the system user still shows as authorized assertTrue("Failed current user auth check on iter "+i, - authManager.authorize(currentUser, Permission.Action.ADMIN)); + authManager.authorize(currentUser, Permission.Action.ADMIN, null)); } } } diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestZKPermissionsWatcher.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestZKPermissionsWatcher.java index b8e7b53..95bab93 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestZKPermissionsWatcher.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestZKPermissionsWatcher.java @@ -93,22 +93,22 @@ User hubert = User.createUserForTesting(conf, "hubert", new String[] { }); assertFalse(AUTH_A.authorizeUser(george, TEST_TABLE, null, - TablePermission.Action.READ)); + TablePermission.Action.READ, null)); assertFalse(AUTH_A.authorizeUser(george, TEST_TABLE, null, - TablePermission.Action.WRITE)); + TablePermission.Action.WRITE, null)); assertFalse(AUTH_A.authorizeUser(hubert, TEST_TABLE, null, - TablePermission.Action.READ)); + TablePermission.Action.READ, null)); assertFalse(AUTH_A.authorizeUser(hubert, TEST_TABLE, null, - TablePermission.Action.WRITE)); + TablePermission.Action.WRITE, null)); assertFalse(AUTH_B.authorizeUser(george, TEST_TABLE, null, - TablePermission.Action.READ)); + TablePermission.Action.READ, null)); assertFalse(AUTH_B.authorizeUser(george, TEST_TABLE, null, - TablePermission.Action.WRITE)); + TablePermission.Action.WRITE, null)); assertFalse(AUTH_B.authorizeUser(hubert, TEST_TABLE, null, - TablePermission.Action.READ)); + TablePermission.Action.READ, null)); assertFalse(AUTH_B.authorizeUser(hubert, TEST_TABLE, null, - TablePermission.Action.WRITE)); + TablePermission.Action.WRITE, null)); // update ACL: george RW List acl = new ArrayList(); @@ -127,21 +127,21 @@ // check it assertTrue(AUTH_A.authorizeUser(george, TEST_TABLE, null, - TablePermission.Action.READ)); + TablePermission.Action.READ, null)); assertTrue(AUTH_A.authorizeUser(george, TEST_TABLE, null, - TablePermission.Action.WRITE)); + TablePermission.Action.WRITE, null)); assertTrue(AUTH_B.authorizeUser(george, TEST_TABLE, null, - TablePermission.Action.READ)); + TablePermission.Action.READ, null)); assertTrue(AUTH_B.authorizeUser(george, TEST_TABLE, null, - TablePermission.Action.WRITE)); + TablePermission.Action.WRITE, null)); assertFalse(AUTH_A.authorizeUser(hubert, TEST_TABLE, null, - TablePermission.Action.READ)); + TablePermission.Action.READ, null)); assertFalse(AUTH_A.authorizeUser(hubert, TEST_TABLE, null, - TablePermission.Action.WRITE)); + TablePermission.Action.WRITE, null)); assertFalse(AUTH_B.authorizeUser(hubert, TEST_TABLE, null, - TablePermission.Action.READ)); + TablePermission.Action.READ, null)); assertFalse(AUTH_B.authorizeUser(hubert, TEST_TABLE, null, - TablePermission.Action.WRITE)); + TablePermission.Action.WRITE, null)); // update ACL: hubert R acl = new ArrayList(); @@ -159,20 +159,20 @@ // check it assertTrue(AUTH_A.authorizeUser(george, TEST_TABLE, null, - TablePermission.Action.READ)); + TablePermission.Action.READ, null)); assertTrue(AUTH_A.authorizeUser(george, TEST_TABLE, null, - TablePermission.Action.WRITE)); + TablePermission.Action.WRITE, null)); assertTrue(AUTH_B.authorizeUser(george, TEST_TABLE, null, - TablePermission.Action.READ)); + TablePermission.Action.READ, null)); assertTrue(AUTH_B.authorizeUser(george, TEST_TABLE, null, - TablePermission.Action.WRITE)); + TablePermission.Action.WRITE, null)); assertTrue(AUTH_A.authorizeUser(hubert, TEST_TABLE, null, - TablePermission.Action.READ)); + TablePermission.Action.READ, null)); assertFalse(AUTH_A.authorizeUser(hubert, TEST_TABLE, null, - TablePermission.Action.WRITE)); + TablePermission.Action.WRITE, null)); assertTrue(AUTH_B.authorizeUser(hubert, TEST_TABLE, null, - TablePermission.Action.READ)); + TablePermission.Action.READ, null)); assertFalse(AUTH_B.authorizeUser(hubert, TEST_TABLE, null, - TablePermission.Action.WRITE)); + TablePermission.Action.WRITE, null)); } }