ShowCreateUserCommand.java

// 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.doris.nereids.trees.plans.commands;

import org.apache.doris.analysis.UserIdentity;
import org.apache.doris.catalog.Column;
import org.apache.doris.catalog.Env;
import org.apache.doris.catalog.ScalarType;
import org.apache.doris.common.AnalysisException;
import org.apache.doris.common.ErrorCode;
import org.apache.doris.common.ErrorReport;
import org.apache.doris.mysql.privilege.PrivPredicate;
import org.apache.doris.nereids.trees.plans.PlanType;
import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
import org.apache.doris.qe.ConnectContext;
import org.apache.doris.qe.ShowResultSet;
import org.apache.doris.qe.ShowResultSetMetaData;
import org.apache.doris.qe.StmtExecutor;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;

import java.util.ArrayList;
import java.util.List;

/**
 * show create user command
 */
public class ShowCreateUserCommand extends ShowCommand {
    private static final ShowResultSetMetaData META_DATA =
            ShowResultSetMetaData.builder()
                .addColumn(new Column("User Identity", ScalarType.createVarchar(512)))
                .addColumn(new Column("Create Stmt", ScalarType.createVarchar(1024)))
                .build();
    private UserIdentity userIdent;

    /**
     * constructor for show create user
     */
    public ShowCreateUserCommand(UserIdentity userIdent) {
        super(PlanType.SHOW_CREATE_USER_COMMAND);
        this.userIdent = userIdent;
    }

    @VisibleForTesting
    protected ShowResultSet handleShowCreateUser(ConnectContext ctx, StmtExecutor executor) throws Exception {
        userIdent.analyze();
        Preconditions.checkState(userIdent != null);
        UserIdentity self = ConnectContext.get().getCurrentUserIdentity();

        // need global GRANT priv.
        if (!self.equals(userIdent)) {
            if (!Env.getCurrentEnv().getAccessManager().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) {
                ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT");
            }
        }
        if (userIdent != null && !Env.getCurrentEnv().getAccessManager().getAuth().doesUserExist(userIdent)) {
            throw new AnalysisException(String.format("User: %s does not exist", userIdent));
        }

        // get all user identity
        List<List<String>> infos = new ArrayList<>();

        // get user's create stmt
        List<String> userInfo = new ArrayList<>();
        userInfo.add(userIdent.getQualifiedUser());
        userInfo.add(toSql(userIdent));
        infos.add(userInfo);

        return new ShowResultSet(getMetaData(), infos);
    }

    @VisibleForTesting
    protected String toSql(UserIdentity user) {
        if (user == null) {
            return "";
        }
        StringBuilder sb = new StringBuilder("CREATE USER ");

        // user identity
        sb.append(user);

        // user password
        sb.append(" IDENTIFIED BY *** ");

        // password policy
        if (Env.getCurrentEnv().getAuth().getPasswdPolicyManager() != null) {
            // policies are : <expirePolicy, historyPolicy, failedLoginPolicy>
            // expirePolicy has two lists: <EXPIRATION_SECONDS>, <PASSWORD_CREATION_TIME>
            // historyPolicy has two lists: <HISTORY_NUM>, <HISTORY_PASSWORDS>
            // failedLoginPolicy has four lists :
            // <NUM_FAILED_LOGIN>, <PASSWORD_LOCK_SECONDS>, <FAILED_LOGIN_COUNTER>, <LOCK_TIME>
            List<List<String>> policies = Env.getCurrentEnv().getAuth().getPasswdPolicyManager().getPolicyInfo(user);
            if (policies != null) {
                // historyPolicy: <HISTORY_NUM>, <HISTORY_PASSWORDS>; use HISTORY_NUM only
                if (policies.size() > 3) {
                    List<String> history = policies.get(2);
                    String historyValue = history.get(1).equalsIgnoreCase("DEFAULT")
                            || history.get(1).equalsIgnoreCase("NO_RESTRICTION") ? "DEFAULT" : history.get(1);
                    sb.append(" PASSWORD_HISTORY ").append(historyValue);
                }

                // expirePolicy: <EXPIRATION_SECONDS>, <PASSWORD_CREATION_TIME>; use EXPIRATION_SECONDS only
                if (policies.size() > 1) {
                    List<String> expire = policies.get(0);
                    String expireValue = expire.get(1);
                    if (expireValue.equalsIgnoreCase("DEFAULT") || expireValue.equalsIgnoreCase("NEVER")) {
                        sb.append(" PASSWORD_EXPIRE ").append(expireValue);
                    } else {
                        sb.append(" PASSWORD_EXPIRE INTERVAL ").append(expireValue).append(" SECOND");
                    }
                }

                // failedLoginPolicy: <NUM_FAILED_LOGIN>, <PASSWORD_LOCK_SECONDS>, <FAILED_LOGIN_COUNTER>, <LOCK_TIME>
                // use NUM_FAILED_LOGIN and PASSWORD_LOCK_SECONDS only
                if (policies.size() > 7) {
                    List<String> failedAttempts = policies.get(4);
                    String attemptValue = failedAttempts.get(1).equalsIgnoreCase("DISABLED") ? "0"
                            : failedAttempts.get(1);
                    sb.append(" FAILED_LOGIN_ATTEMPTS ").append(attemptValue);

                    List<String> lockTime = policies.get(5);
                    String lockValue = lockTime.get(1).equalsIgnoreCase("DISABLED") ? "UNBOUNDED" : lockTime.get(1);
                    if (lockValue.equalsIgnoreCase("DISABLED") || lockValue.equalsIgnoreCase("UNBOUNDED")) {
                        sb.append(" PASSWORD_LOCK_TIME UNBOUNDED");
                    } else {
                        sb.append(" PASSWORD_LOCK_TIME ").append(lockValue).append(" SECOND");
                    }
                }
            }
        }

        // comment
        if (Env.getCurrentEnv().getAuth().getUserManager().getUserByUserIdentity(user) != null) {
            String comment = Env.getCurrentEnv().getAuth().getUserManager().getUserByUserIdentity(user).getComment();
            if (comment != null) {
                sb.append(" COMMENT ").append("\"").append(comment).append("\"");
            }
        }
        return sb.toString();
    }

    @Override
    public ShowResultSet doRun(ConnectContext ctx, StmtExecutor executor) throws Exception {
        return handleShowCreateUser(ctx, executor);
    }

    @Override
    public <R, C> R accept(PlanVisitor<R, C> visitor, C context) {
        return visitor.visitShowCreateUserCommand(this, context);
    }

    @Override
    public ShowResultSetMetaData getMetaData() {
        return META_DATA;
    }
}