blob: 38f8c689b4f8a6400ce67f38e843594a9afd7539 [file] [log] [blame]
/*
* 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.utils;
import org.apache.iotdb.commons.auth.AuthException;
import org.apache.iotdb.commons.auth.entity.PathPrivilege;
import org.apache.iotdb.commons.auth.entity.PriPrivilegeType;
import org.apache.iotdb.commons.auth.entity.PrivilegeType;
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.exception.IllegalPathException;
import org.apache.iotdb.commons.path.PartialPath;
import org.apache.iotdb.commons.path.PathDeserializeUtil;
import org.apache.iotdb.commons.path.PathPatternUtil;
import org.apache.iotdb.commons.security.encrypt.AsymmetricEncryptFactory;
import org.apache.iotdb.confignode.rpc.thrift.TPermissionInfoResp;
import org.apache.iotdb.confignode.rpc.thrift.TRoleResp;
import org.apache.iotdb.confignode.rpc.thrift.TUserResp;
import org.apache.iotdb.rpc.TSStatusCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class AuthUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(AuthUtils.class);
private static final String ROOT_PREFIX = IoTDBConstant.PATH_ROOT;
private static final int MIN_LENGTH = 4;
private static final int MAX_LENGTH = 32;
// match number, character, and !@#$%^*()_+-=
// pattern: ^[-\w!@#\$%\^\(\)\+=]*$
private static final String REX_PATTERN = "^[-\\w!@#$%^&*()+=]*$";
private AuthUtils() {
// Empty constructor
}
/**
* This filed only for pre version. When we do a major version upgrade, it can be removed
* directly.
*/
// FOR PRE VERSION BEGIN -----
private static final int MAX_LENGTH_PRE = 64;
private static final String REX_PATTERN_PRE = "^[-\\w]*$";
public static void validatePasswordPre(String password) throws AuthException {
validateNameOrPasswordPre(password);
}
public static void validateUsernamePre(String username) throws AuthException {
validateNameOrPasswordPre(username);
}
public static void validateRolenamePre(String rolename) throws AuthException {
validateNameOrPasswordPre(rolename);
}
public static void validateNameOrPasswordPre(String str) throws AuthException {
int length = str.length();
if (length < MIN_LENGTH) {
throw new AuthException(
TSStatusCode.ILLEGAL_PARAMETER,
"The length of name or password must be greater than or equal to " + MIN_LENGTH);
} else if (length > MAX_LENGTH_PRE) {
throw new AuthException(
TSStatusCode.ILLEGAL_PARAMETER,
"The length of name or password must be less than or equal to " + MAX_LENGTH);
} else if (str.contains(" ")) {
throw new AuthException(
TSStatusCode.ILLEGAL_PARAMETER, "The name or password cannot contain spaces");
} else if (!str.matches(REX_PATTERN_PRE)) {
throw new AuthException(
TSStatusCode.ILLEGAL_PARAMETER,
"The name or password can only contain letters, numbers, and underscores");
}
}
// FOR PRE VERSION DONE ---
/**
* Validate password
*
* @param password user password
* @throws AuthException contains message why password is invalid
*/
public static void validatePassword(String password) throws AuthException {
validateNameOrPassword(password);
}
/**
* Checking whether origin password is mapping to encrypt password by encryption
*
* @param originPassword the password before encryption
* @param encryptPassword the password after encryption
*/
public static boolean validatePassword(String originPassword, String encryptPassword) {
return AsymmetricEncryptFactory.getEncryptProvider(
CommonDescriptor.getInstance().getConfig().getEncryptDecryptProvider(),
CommonDescriptor.getInstance().getConfig().getEncryptDecryptProviderParameter())
.validate(originPassword, encryptPassword);
}
/**
* Validate username
*
* @param username username
* @throws AuthException contains message why username is invalid
*/
public static void validateUsername(String username) throws AuthException {
validateNameOrPassword(username);
}
/**
* Validate role name
*
* @param rolename role name
* @throws AuthException contains message why rolename is invalid
*/
public static void validateRolename(String rolename) throws AuthException {
validateNameOrPassword(rolename);
}
public static void validateNameOrPassword(String str) throws AuthException {
int length = str.length();
if (length < MIN_LENGTH) {
throw new AuthException(
TSStatusCode.ILLEGAL_PARAMETER,
"The length of name or password must be greater than or equal to " + MIN_LENGTH);
} else if (length > MAX_LENGTH) {
throw new AuthException(
TSStatusCode.ILLEGAL_PARAMETER,
"The length of name or password must be less than or equal to " + MAX_LENGTH);
} else if (str.contains(" ")) {
throw new AuthException(
TSStatusCode.ILLEGAL_PARAMETER, "The name or password cannot contain spaces");
} else if (!str.matches(REX_PATTERN)) {
throw new AuthException(
TSStatusCode.ILLEGAL_PARAMETER,
"The name or password can only contain letters, numbers or !@#$%^*()_+-=");
}
}
/**
* Validate path
*
* @param path series path
* @throws AuthException contains message why path is invalid
*/
public static void validatePath(PartialPath path) throws AuthException {
if (!path.getFirstNode().equals(ROOT_PREFIX)) {
throw new AuthException(
TSStatusCode.ILLEGAL_PARAMETER,
String.format(
"Illegal seriesPath %s, seriesPath should start with \"%s\"", path, ROOT_PREFIX));
}
}
public static void validatePatternPath(PartialPath path) throws AuthException {
validatePath(path);
if (!path.hasWildcard()) {
return;
} else if (!PathPatternUtil.isMultiLevelMatchWildcard(path.getTailNode())) {
// check a.b.*.c/ a.b.**.c/ a.b*.c/ a.b.c.*
throw new AuthException(
TSStatusCode.ILLEGAL_PARAMETER,
String.format(
"Illegal pattern path: %s, only pattern path that end with ** are supported.", path));
}
for (int i = 0; i < path.getNodeLength() - 1; i++) {
if (PathPatternUtil.hasWildcard(path.getNodes()[i])) {
throw new AuthException(
TSStatusCode.ILLEGAL_PARAMETER,
String.format(
"Illegal pattern path: %s, only pattern path that end with wildcards are supported.",
path));
}
}
}
public static PartialPath convertPatternPath(PartialPath path) throws IllegalPathException {
String pathStr = new String();
int i = 0;
for (; i < path.getNodeLength(); i++) {
if (!PathPatternUtil.hasWildcard(path.getNodes()[i])) {
pathStr = pathStr.concat(path.getNodes()[i] + ".");
} else {
break;
}
}
pathStr = pathStr.concat("**");
return new PartialPath(pathStr);
}
/**
* Encrypt password
*
* @param password password
* @return encrypted password if success
*/
public static String encryptPassword(String password) {
return AsymmetricEncryptFactory.getEncryptProvider(
CommonDescriptor.getInstance().getConfig().getEncryptDecryptProvider(),
CommonDescriptor.getInstance().getConfig().getEncryptDecryptProviderParameter())
.encrypt(password);
}
/**
* Check path privilege
*
* @param path series path
* @param privilegeId privilege Id
* @param privilegeList privileges in List structure
* @exception AuthException throw if path is invalid or path in privilege is invalid
* @return True if privilege-check passed
*/
public static boolean checkPathPrivilege(
PartialPath path, int privilegeId, List<PathPrivilege> privilegeList) {
if (privilegeList == null) {
return false;
}
for (PathPrivilege pathPrivilege : privilegeList) {
if (pathPrivilege.getPath().matchFullPath(path)
&& pathPrivilege.checkPrivilege(privilegeId)) {
return true;
}
}
return false;
}
public static boolean checkPathPrivilegeGrantOpt(
PartialPath path, int privilegeId, List<PathPrivilege> privilegeList) {
if (privilegeList == null) {
return false;
}
for (PathPrivilege pathPrivilege : privilegeList) {
if (pathPrivilege.getPath().matchFullPath(path)
&& pathPrivilege.getPrivileges().contains(privilegeId)
&& pathPrivilege.getGrantOpt().contains(privilegeId)) {
return true;
}
}
return false;
}
/**
* Get privileges
*
* @param path The seriesPath on which the privileges take effect.
* @exception AuthException throw if path is invalid or path in privilege is invalid
* @return The privileges granted to the role
*/
public static Set<Integer> getPrivileges(PartialPath path, List<PathPrivilege> privilegeList) {
if (privilegeList == null) {
return new HashSet<>();
}
Set<Integer> privileges = new HashSet<>();
for (PathPrivilege pathPrivilege : privilegeList) {
if (pathPrivilege.getPath().matchFullPath(path)) {
privileges.addAll(pathPrivilege.getPrivileges());
}
}
return privileges;
}
/**
* Check if series path has this privilege to revoke
*
* @param path series path
* @param privilegeId privilege Id
* @param privilegeList privileges in List structure
* @return True if series path has this privilege
*/
public static boolean hasPrivilegeToReovke(
PartialPath path, int privilegeId, List<PathPrivilege> privilegeList) {
for (PathPrivilege pathPrivilege : privilegeList) {
if (path.matchFullPath(pathPrivilege.getPath())
&& pathPrivilege.getPrivileges().contains(privilegeId)) {
return true;
}
}
return false;
}
public static boolean hasPrivilege(
PartialPath path, int privilegeId, List<PathPrivilege> privilegeList) {
for (PathPrivilege pathPrivilege : privilegeList) {
if (pathPrivilege.getPath().equals(path)
&& pathPrivilege.getPrivileges().contains(privilegeId)) {
return true;
}
}
return false;
}
/**
* Add privilege
*
* @param path series path
* @param privilegeId privilege Id
* @param privilegeList privileges in List structure of user or role
*/
public static void addPrivilege(
PartialPath path, int privilegeId, List<PathPrivilege> privilegeList, boolean grantOption) {
PathPrivilege targetPathPrivilege = null;
// check PathPrivilege of target path is already existed
for (PathPrivilege pathPrivilege : privilegeList) {
if (pathPrivilege.getPath().equals(path)) {
targetPathPrivilege = pathPrivilege;
break;
}
}
// if not, then create new PathPrivilege
if (targetPathPrivilege == null) {
targetPathPrivilege = new PathPrivilege(path);
privilegeList.add(targetPathPrivilege);
}
// add privilegeId into targetPathPrivilege
targetPathPrivilege.grantPrivilege(privilegeId, grantOption);
}
/**
* Remove privilege
*
* @param path series path
* @param privilegeId privilege Id
* @param privilegeList privileges in List structure of user or role
*/
public static void removePrivilege(
PartialPath path, int privilegeId, List<PathPrivilege> privilegeList) {
Iterator<PathPrivilege> it = privilegeList.iterator();
while (it.hasNext()) {
PathPrivilege pathPri = it.next();
if (path.matchFullPath(pathPri.getPath())) {
pathPri.revokePrivilege(privilegeId);
if (pathPri.getPrivileges().isEmpty()) {
it.remove();
}
}
}
}
public static void removePrivilegePre(
PartialPath path, int privilegeId, List<PathPrivilege> privilegeList) {
Iterator<PathPrivilege> it = privilegeList.iterator();
while (it.hasNext()) {
PathPrivilege pathPri = it.next();
if (pathPri.getPath().equals(path)) {
if (privilegeId != PriPrivilegeType.ALL.ordinal()) {
pathPri.revokePrivilege(privilegeId);
} else {
it.remove();
return;
}
if (pathPri.getPrivileges().isEmpty()) {
it.remove();
}
}
}
}
/** Generate empty permission response when failed */
public static TPermissionInfoResp generateEmptyPermissionInfoResp() {
TPermissionInfoResp permissionInfoResp = new TPermissionInfoResp();
permissionInfoResp.setUserInfo(
new TUserResp(
"", "", new ArrayList<>(), new HashSet<>(), new HashSet<>(), new ArrayList<>(), false));
Map<String, TRoleResp> roleInfo = new HashMap<>();
roleInfo.put("", new TRoleResp("", new ArrayList<>(), new HashSet<>(), new HashSet<>()));
permissionInfoResp.setRoleInfo(roleInfo);
return permissionInfoResp;
}
/**
* Transform permission from name to privilegeId
*
* @param authorizationList the list of privilege name
* @return the list of privilege Ids
* @throws AuthException throws if there are no privilege matched
*/
public static Set<Integer> strToPermissions(String[] authorizationList) throws AuthException {
Set<Integer> result = new HashSet<>();
if (authorizationList == null) {
return result;
}
PrivilegeType[] types = PrivilegeType.values();
for (String authorization : authorizationList) {
boolean legal = false;
for (PrivilegeType privilegeType : types) {
if (authorization.equalsIgnoreCase(privilegeType.name())) {
result.add(privilegeType.ordinal());
legal = true;
break;
}
}
if (!legal) {
throw new AuthException(
TSStatusCode.UNKNOWN_AUTH_PRIVILEGE, "No such privilege " + authorization);
}
}
return result;
}
public static ByteBuffer serializePartialPathList(List<PartialPath> paths) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
DataOutputStream dataOutputStream = new DataOutputStream(byteArrayOutputStream);
try {
dataOutputStream.writeInt(paths.size());
for (PartialPath path : paths) {
path.serialize(dataOutputStream);
}
} catch (IOException e) {
LOGGER.error("Failed to serialize PartialPath list", e);
}
return ByteBuffer.wrap(byteArrayOutputStream.toByteArray());
}
public static List<PartialPath> deserializePartialPathList(ByteBuffer buffer) {
int size = buffer.getInt();
List<PartialPath> paths = new ArrayList<>();
for (int i = 0; i < size; i++) {
paths.add((PartialPath) PathDeserializeUtil.deserialize(buffer));
}
return paths;
}
public static void checkAndRefreshPri(Role role) {
if (role.getServiceReady()) {
return;
}
Set<Integer> sysPriCopy = role.getSysPrivilege();
List<PathPrivilege> priCopy = role.getPathPrivilegeList();
role.setSysPrivilegeSet(new HashSet<>());
role.setPrivilegeList(new ArrayList<>());
// Pre version's privileges were stored in path list;
for (PathPrivilege pathPri : priCopy) {
PartialPath path = pathPri.getPath();
for (int prePri : pathPri.getPrivileges()) {
PriPrivilegeType type = PriPrivilegeType.values()[prePri];
if (type.isAccept()) {
for (PrivilegeType curType : type.getSubPri()) {
if (curType.isPathRelevant()) {
try {
AuthUtils.validatePatternPath(path);
} catch (AuthException e) {
try {
path = AuthUtils.convertPatternPath(path);
} catch (IllegalPathException illegalE) {
// will never get here
String[] str = {"root", "**"};
path = new PartialPath(str);
}
}
role.addPathPrivilege(path, curType.ordinal(), false);
} else {
role.addSysPrivilege(curType.ordinal());
}
}
}
}
}
role.setServiceReady(true);
}
public static int posToSysPri(int pos) {
switch (pos) {
case 0:
return PrivilegeType.MANAGE_DATABASE.ordinal();
case 1:
return PrivilegeType.MANAGE_USER.ordinal();
case 2:
return PrivilegeType.MANAGE_ROLE.ordinal();
case 3:
return PrivilegeType.USE_TRIGGER.ordinal();
case 4:
return PrivilegeType.USE_UDF.ordinal();
case 5:
return PrivilegeType.USE_CQ.ordinal();
case 6:
return PrivilegeType.USE_PIPE.ordinal();
case 7:
return PrivilegeType.EXTEND_TEMPLATE.ordinal();
case 8:
return PrivilegeType.MAINTAIN.ordinal();
case 9:
return PrivilegeType.USE_MODEL.ordinal();
default:
return -1;
}
}
public static int sysPriTopos(int privilegeId) {
PrivilegeType type = PrivilegeType.values()[privilegeId];
switch (type) {
case MANAGE_DATABASE:
return 0;
case MANAGE_USER:
return 1;
case MANAGE_ROLE:
return 2;
case USE_TRIGGER:
return 3;
case USE_UDF:
return 4;
case USE_CQ:
return 5;
case USE_PIPE:
return 6;
case EXTEND_TEMPLATE:
return 7;
case MAINTAIN:
return 8;
case USE_MODEL:
return 9;
default:
return -1;
}
}
public static int pathPosToPri(int pos) {
switch (pos) {
case 0:
return PrivilegeType.READ_DATA.ordinal();
case 1:
return PrivilegeType.WRITE_DATA.ordinal();
case 2:
return PrivilegeType.READ_SCHEMA.ordinal();
case 3:
return PrivilegeType.WRITE_SCHEMA.ordinal();
default:
return -1;
}
}
public static int pathPriToPos(PrivilegeType pri) {
switch (pri) {
case READ_DATA:
return 0;
case WRITE_DATA:
return 1;
case READ_SCHEMA:
return 2;
case WRITE_SCHEMA:
return 3;
default:
return -1;
}
}
}