| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 org.apache.iotdb.commons.auth.role; |
| |
| import org.apache.iotdb.commons.auth.entity.PathPrivilege; |
| import org.apache.iotdb.commons.auth.entity.Role; |
| import org.apache.iotdb.commons.conf.CommonDescriptor; |
| import org.apache.iotdb.commons.conf.IoTDBConstant; |
| import org.apache.iotdb.commons.file.SystemFileFactory; |
| import org.apache.iotdb.commons.utils.FileUtils; |
| import org.apache.iotdb.commons.utils.IOUtils; |
| import org.apache.iotdb.commons.utils.TestOnly; |
| import org.apache.iotdb.tsfile.utils.Pair; |
| |
| import org.apache.thrift.TException; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import java.io.BufferedInputStream; |
| import java.io.BufferedOutputStream; |
| import java.io.DataInputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.nio.ByteBuffer; |
| import java.nio.file.Files; |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.UUID; |
| |
| /** |
| * This class store role info in a separate sequential file and every role will have one role file. |
| * Role file schema: rolename-length int32| rolename-bytes-utf8| system-perivileges int32| |
| * path[1]-length int32| path[1]-bytes-utf8 | privilege-int32| path[2]-length int32| |
| * path[2]-bytes-utf8 | privilege-int32| path[3]-length int32| path[3]-bytes-utf8 | privilege-int32| |
| * ..... |
| * |
| * <p>system-privileges is an int32 mask code. Low 16 bits for privileges and high 16 bits mark that |
| * whether the corresponding position 's privilege has grant option.So we can use a int32 to mark 16 |
| * kinds of privileges. Here are the permissions corresponding to the bit location identifier: |
| * Manage_database : 0 MANAGE_USER : 1 MANAGE_ROLE : 2 USE_TRIGGER : 3 USE_UDF : 4 USE_CQ : 5 |
| * USE_PIPE : 6 EXTEDN_TEMPLATE : 7 AUDIT : 8 MAINTAIN : 9 |0000-0000-0000-0001|0000-0000-0000-0001| |
| * means this role has Manage_database's privilege and be able to grant it to others. |
| * |
| * <p>path-privileges is a pair contains paths and privileges mask. Path privilege is a int32 mask |
| * code which has the same structure as system-privileges. For path privilege, the low 16 bits stand |
| * for four privileges, they are: READ_DATA : 0 WRITE_DATA : 1 READ_SCHEMA : 2 WRITE_SCHEMA : 3 And |
| * the high 16 bits stand for grant option. |
| * |
| * <p>Role files is appendable. |
| */ |
| |
| /** |
| * All user/role file store in config node's user/role folder. when our system start, we load all |
| * user/role from folder. If we did some alter query, we just store raft log. |
| */ |
| public class LocalFileRoleAccessor implements IRoleAccessor { |
| private static final Logger LOGGER = LoggerFactory.getLogger(LocalFileRoleAccessor.class); |
| private static final String TEMP_SUFFIX = ".temp"; |
| private static final String STRING_ENCODING = "utf-8"; |
| private static final String ROLE_SNAPSHOT_FILE_NAME = "system" + File.separator + "roles"; |
| |
| private final String roleDirPath; |
| |
| /** |
| * Reused buffer for primitive types encoding/decoding, which aim to reduce memory fragments. Use |
| * ThreadLocal for thread safety. |
| */ |
| private final ThreadLocal<ByteBuffer> encodingBufferLocal = new ThreadLocal<>(); |
| |
| private final ThreadLocal<byte[]> strBufferLocal = new ThreadLocal<>(); |
| |
| public LocalFileRoleAccessor(String roleDirPath) { |
| this.roleDirPath = roleDirPath; |
| } |
| |
| /** |
| * @return role struct |
| * @throws IOException |
| */ |
| @Override |
| public Role loadRole(String rolename) throws IOException { |
| File roleProfile = |
| SystemFileFactory.INSTANCE.getFile( |
| roleDirPath + File.separator + rolename + IoTDBConstant.PROFILE_SUFFIX); |
| if (!roleProfile.exists() || !roleProfile.isFile()) { |
| // System may crush before a newer file is written, so search for back-up file. |
| File backProfile = |
| SystemFileFactory.INSTANCE.getFile( |
| roleDirPath + File.separator + rolename + IoTDBConstant.PROFILE_SUFFIX + TEMP_SUFFIX); |
| if (backProfile.exists() && backProfile.isFile()) { |
| roleProfile = backProfile; |
| } else { |
| return null; |
| } |
| } |
| FileInputStream inputStream = new FileInputStream(roleProfile); |
| try (DataInputStream dataInputStream = |
| new DataInputStream(new BufferedInputStream(inputStream))) { |
| Role role = new Role(); |
| Pair<String, Boolean> result = |
| IOUtils.readAuthString(dataInputStream, STRING_ENCODING, strBufferLocal); |
| role.setName(result.getLeft()); |
| boolean oldVersion = result.getRight(); |
| if (oldVersion) { |
| IOUtils.loadRolePrivilege(role, dataInputStream, STRING_ENCODING, strBufferLocal); |
| return role; |
| } else { |
| role.setSysPrivilegeSet(dataInputStream.readInt()); |
| List<PathPrivilege> pathPrivilegeList = new ArrayList<>(); |
| for (int i = 0; dataInputStream.available() != 0; i++) { |
| pathPrivilegeList.add( |
| IOUtils.readPathPrivilege(dataInputStream, STRING_ENCODING, strBufferLocal, false)); |
| } |
| role.setPrivilegeList(pathPrivilegeList); |
| return role; |
| } |
| } catch (Exception e) { |
| throw new IOException(e); |
| } finally { |
| strBufferLocal.remove(); |
| } |
| } |
| |
| @Override |
| public void saveRole(Role role) throws IOException { |
| File roleProfile = |
| SystemFileFactory.INSTANCE.getFile( |
| roleDirPath |
| + File.separator |
| + role.getName() |
| + IoTDBConstant.PROFILE_SUFFIX |
| + TEMP_SUFFIX); |
| File roleDir = new File(roleDirPath); |
| if (!roleDir.exists() && !roleDir.mkdirs()) { |
| LOGGER.error("Failed to create role dir {}", roleDirPath); |
| } |
| |
| try (FileOutputStream fileOutputStream = new FileOutputStream(roleProfile); |
| BufferedOutputStream outputStream = new BufferedOutputStream(fileOutputStream)) { |
| byte[] strBuffer = role.getName().getBytes(STRING_ENCODING); |
| IOUtils.writeInt(outputStream, -1 * strBuffer.length, encodingBufferLocal); |
| outputStream.write(strBuffer); |
| IOUtils.writeInt(outputStream, role.getAllSysPrivileges(), encodingBufferLocal); |
| int privilegeNum = role.getPathPrivilegeList().size(); |
| for (int i = 0; i < privilegeNum; i++) { |
| PathPrivilege pathPrivilege = role.getPathPrivilegeList().get(i); |
| IOUtils.writePathPrivilege( |
| outputStream, pathPrivilege, STRING_ENCODING, encodingBufferLocal); |
| } |
| // handle outputstream's exception in saveRole locally. |
| outputStream.flush(); |
| fileOutputStream.getFD().sync(); |
| } catch (Exception e) { |
| LOGGER.warn("meet error when save role: {}", role.getName()); |
| throw new IOException(e); |
| } finally { |
| encodingBufferLocal.remove(); |
| } |
| |
| File oldFile = |
| SystemFileFactory.INSTANCE.getFile( |
| roleDirPath + File.separator + role.getName() + IoTDBConstant.PROFILE_SUFFIX); |
| IOUtils.replaceFile(roleProfile, oldFile); |
| } |
| |
| @Override |
| public boolean deleteRole(String rolename) throws IOException { |
| File roleProfile = |
| SystemFileFactory.INSTANCE.getFile( |
| roleDirPath + File.separator + rolename + IoTDBConstant.PROFILE_SUFFIX); |
| File backFile = |
| SystemFileFactory.INSTANCE.getFile( |
| roleDirPath + File.separator + rolename + IoTDBConstant.PROFILE_SUFFIX + TEMP_SUFFIX); |
| if (!roleProfile.exists() && !backFile.exists()) { |
| return false; |
| } |
| if ((roleProfile.exists() && !roleProfile.delete()) |
| || (backFile.exists() && !backFile.delete())) { |
| throw new IOException(String.format("Cannot delete role file of %s", rolename)); |
| } |
| return true; |
| } |
| |
| @Override |
| public List<String> listAllRoles() { |
| File roleDir = SystemFileFactory.INSTANCE.getFile(roleDirPath); |
| String[] names = |
| roleDir.list( |
| (dir, name) -> |
| name.endsWith(IoTDBConstant.PROFILE_SUFFIX) || name.endsWith(TEMP_SUFFIX)); |
| List<String> retList = new ArrayList<>(); |
| if (names != null) { |
| // in very rare situations, normal file and backup file may exist at the same time |
| // so a set is used to deduplicate |
| Set<String> set = new HashSet<>(); |
| for (String fileName : names) { |
| set.add(fileName.replace(IoTDBConstant.PROFILE_SUFFIX, "").replace(TEMP_SUFFIX, "")); |
| } |
| retList.addAll(set); |
| } |
| return retList; |
| } |
| |
| @Override |
| public boolean processTakeSnapshot(File snapshotDir) throws TException, IOException { |
| SystemFileFactory systemFileFactory = SystemFileFactory.INSTANCE; |
| File roleFolder = systemFileFactory.getFile(roleDirPath); |
| File roleSnapshotDir = systemFileFactory.getFile(snapshotDir, ROLE_SNAPSHOT_FILE_NAME); |
| File roleTmpSnapshotDir = |
| systemFileFactory.getFile(roleSnapshotDir.getAbsolutePath() + "-" + UUID.randomUUID()); |
| |
| boolean result = true; |
| try { |
| result = FileUtils.copyDir(roleFolder, roleTmpSnapshotDir); |
| result &= roleTmpSnapshotDir.renameTo(roleSnapshotDir); |
| } finally { |
| if (roleTmpSnapshotDir.exists() && !roleTmpSnapshotDir.delete()) { |
| FileUtils.deleteFileOrDirectory(roleTmpSnapshotDir); |
| } |
| } |
| return result; |
| } |
| |
| @Override |
| public void processLoadSnapshot(File snapshotDir) throws TException, IOException { |
| SystemFileFactory systemFileFactory = SystemFileFactory.INSTANCE; |
| File roleFolder = systemFileFactory.getFile(roleDirPath); |
| File roleTmpFolder = |
| systemFileFactory.getFile(roleFolder.getAbsolutePath() + "-" + UUID.randomUUID()); |
| File roleSnapshotDir = systemFileFactory.getFile(snapshotDir, ROLE_SNAPSHOT_FILE_NAME); |
| if (roleSnapshotDir.exists()) { |
| try { |
| org.apache.commons.io.FileUtils.moveDirectory(roleFolder, roleTmpFolder); |
| if (!FileUtils.copyDir(roleSnapshotDir, roleFolder)) { |
| LOGGER.error("Failed to load role folder snapshot and rollback."); |
| // rollback if failed to copy |
| FileUtils.deleteFileOrDirectory(roleFolder); |
| org.apache.commons.io.FileUtils.moveDirectory(roleTmpFolder, roleFolder); |
| } |
| } finally { |
| FileUtils.deleteFileOrDirectory(roleTmpFolder); |
| } |
| } else { |
| LOGGER.info("There are no roles to load."); |
| } |
| } |
| |
| @Override |
| public void reset() { |
| checkOldRoleDir(SystemFileFactory.INSTANCE.getFile(roleDirPath)); |
| if (SystemFileFactory.INSTANCE.getFile(roleDirPath).mkdirs()) { |
| LOGGER.info("role info dir {} is created", roleDirPath); |
| } else if (!SystemFileFactory.INSTANCE.getFile(roleDirPath).exists()) { |
| LOGGER.error("role info dir {} can not be created", roleDirPath); |
| } |
| } |
| |
| private void checkOldRoleDir(File newDir) { |
| File oldDir = new File(CommonDescriptor.getInstance().getConfig().getOldRoleFolder()); |
| if (oldDir.exists()) { |
| if (!FileUtils.moveFileSafe(oldDir, newDir)) { |
| LOGGER.error("move old role dir fail: {}", oldDir.getAbsolutePath()); |
| } |
| } |
| } |
| |
| @Override |
| public void cleanRoleFolder() { |
| File[] files = SystemFileFactory.INSTANCE.getFile(roleDirPath).listFiles(); |
| if (files != null) { |
| for (File file : files) { |
| FileUtils.deleteFileIfExist(file); |
| } |
| } else { |
| LOGGER.warn("Role folder not exists"); |
| } |
| } |
| |
| @TestOnly |
| public void saveRoleOldVer(Role role) throws IOException { |
| File roleProfile = |
| SystemFileFactory.INSTANCE.getFile( |
| roleDirPath |
| + File.separator |
| + role.getName() |
| + IoTDBConstant.PROFILE_SUFFIX |
| + TEMP_SUFFIX); |
| File roleDir = new File(roleDirPath); |
| if (!roleDir.exists()) { |
| if (!roleDir.mkdirs()) { |
| LOGGER.error("Failed to create role dir {}", roleDirPath); |
| } |
| } |
| try (BufferedOutputStream outputStream = |
| new BufferedOutputStream(Files.newOutputStream(roleProfile.toPath()))) { |
| try { |
| IOUtils.writeString(outputStream, role.getName(), STRING_ENCODING, encodingBufferLocal); |
| int privilegeNum = role.getPathPrivilegeList().size(); |
| IOUtils.writeInt(outputStream, privilegeNum, encodingBufferLocal); |
| for (int i = 0; i < privilegeNum; i++) { |
| PathPrivilege pathPrivilege = role.getPathPrivilegeList().get(i); |
| IOUtils.writeString( |
| outputStream, |
| pathPrivilege.getPath().getFullPath(), |
| STRING_ENCODING, |
| encodingBufferLocal); |
| IOUtils.writeInt(outputStream, pathPrivilege.getPrivileges().size(), encodingBufferLocal); |
| for (Integer item : pathPrivilege.getPrivileges()) { |
| IOUtils.writeInt(outputStream, item, encodingBufferLocal); |
| } |
| } |
| outputStream.flush(); |
| } catch (Exception e) { |
| throw new IOException(e); |
| } |
| } finally { |
| encodingBufferLocal.remove(); |
| } |
| |
| File oldFile = |
| SystemFileFactory.INSTANCE.getFile( |
| roleDirPath + File.separator + role.getName() + IoTDBConstant.PROFILE_SUFFIX); |
| IOUtils.replaceFile(roleProfile, oldFile); |
| } |
| } |