FileBasedAuth.java

/*
 * This file is part of Waarp Project (named also Waarp or GG).
 *
 *  Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
 *  tags. See the COPYRIGHT.txt in the distribution for a full listing of
 * individual contributors.
 *
 *  All Waarp Project is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along with
 * Waarp . If not, see <http://www.gnu.org/licenses/>.
 */

package org.waarp.gateway.ftp.file;

import org.waarp.common.command.NextCommandReply;
import org.waarp.common.command.ReplyCode;
import org.waarp.common.command.exception.Reply421Exception;
import org.waarp.common.command.exception.Reply530Exception;
import org.waarp.common.database.DbConstant;
import org.waarp.common.file.DirInterface;
import org.waarp.common.logging.WaarpLogger;
import org.waarp.common.logging.WaarpLoggerFactory;
import org.waarp.ftp.core.command.FtpCommandCode;
import org.waarp.ftp.core.session.FtpSession;
import org.waarp.ftp.filesystembased.FilesystemBasedFtpAuth;
import org.waarp.ftp.filesystembased.FilesystemBasedFtpRestart;
import org.waarp.gateway.ftp.config.FileBasedConfiguration;
import org.waarp.gateway.ftp.exec.AbstractExecutor.CommandExecutor;
import org.waarp.gateway.kernel.session.HttpAuthInterface;

import java.io.File;

/**
 * FtpAuth implementation based on a list of (user/password/account) stored in a
 * xml file load at startup from
 * configuration.
 */
public class FileBasedAuth extends FilesystemBasedFtpAuth
    implements HttpAuthInterface {
  /**
   * Internal Logger
   */
  private static final WaarpLogger logger =
      WaarpLoggerFactory.getLogger(FileBasedAuth.class);

  /**
   * Current authentication
   */
  private SimpleAuth currentAuth;

  /**
   * Special Id for the current transfer
   */
  private long specialId = DbConstant.ILLEGALVALUE;

  /**
   * @param session
   */
  public FileBasedAuth(final FtpSession session) {
    super(session);
  }

  @Override
  protected final void businessClean() {
    currentAuth = null;
  }

  /**
   * @param user the user to set
   *
   * @return (NOOP, 230) if the user is OK, else return the following command
   *     that must follow (usually PASS) and
   *     the associated reply
   *
   * @throws Reply421Exception if there is a problem during the
   *     authentication
   * @throws Reply530Exception if there is a problem during the
   *     authentication
   */
  @Override
  protected final NextCommandReply setBusinessUser(final String user)
      throws Reply530Exception {
    final SimpleAuth auth =
        ((FileBasedConfiguration) ((FtpSession) getSession()).getConfiguration()).getSimpleAuth(
            user);
    if (auth == null) {
      setIsIdentified(false);
      currentAuth = null;
      throw new Reply530Exception("User name not allowed");
    }
    currentAuth = auth;
    return new NextCommandReply(FtpCommandCode.PASS,
                                ReplyCode.REPLY_331_USER_NAME_OKAY_NEED_PASSWORD,
                                null);
  }

  /**
   * Set the password according to any implementation and could set the
   * rootFromAuth. If NOOP is returned,
   * isIdentifed must be TRUE. A special case is implemented for test user.
   *
   * @param password
   *
   * @return (NOOP, 230) if the Password is OK, else return the following
   *     command that must follow (usually ACCT)
   *     and the associated reply
   *
   * @throws Reply421Exception if there is a problem during the
   *     authentication
   * @throws Reply530Exception if there is a problem during the
   *     authentication
   */
  @Override
  protected final NextCommandReply setBusinessPassword(final String password)
      throws Reply530Exception {
    if (currentAuth == null) {
      setIsIdentified(false);
      throw new Reply530Exception("PASS needs a USER first");
    }
    if (currentAuth.isPasswordValid(password)) {
      return new NextCommandReply(FtpCommandCode.ACCT,
                                  ReplyCode.REPLY_332_NEED_ACCOUNT_FOR_LOGIN,
                                  null);
    }
    throw new Reply530Exception("Password is not valid");
  }

  /**
   * Set the account according to any implementation and could set the
   * rootFromAuth. If NOOP is returned,
   * isIdentifed must be TRUE.
   *
   * @param account
   *
   * @return (NOOP, 230) if the Account is OK, else return the following
   *     command
   *     that must follow and the
   *     associated reply
   *
   * @throws Reply421Exception if there is a problem during the
   *     authentication
   * @throws Reply530Exception if there is a problem during the
   *     authentication
   */
  @Override
  protected final NextCommandReply setBusinessAccount(final String account)
      throws Reply530Exception {
    if (currentAuth == null) {
      throw new Reply530Exception("ACCT needs a USER first");
    }
    if (currentAuth.isAccountValid(account)) {
      setIsIdentified(true);
      logger.info("User {} is authentified with account {}", user, account);
      return new NextCommandReply(FtpCommandCode.NOOP,
                                  ReplyCode.REPLY_230_USER_LOGGED_IN, null);
    }
    throw new Reply530Exception("Account is not valid");
  }

  @Override
  public final boolean isBusinessPathValid(final String newPath) {
    if (newPath == null) {
      return false;
    }
    return newPath.startsWith(getBusinessPath());
  }

  @Override
  protected final String setBusinessRootFromAuth() throws Reply421Exception {
    final String path;
    if (account == null) {
      path = DirInterface.SEPARATOR + user;
    } else {
      path = DirInterface.SEPARATOR + user + DirInterface.SEPARATOR + account;
    }
    final String fullpath = getAbsolutePath(path);
    final File file = new File(fullpath);
    if (!file.isDirectory()) {
      throw new Reply421Exception("Filesystem not ready");
    }
    return path;
  }

  @Override
  public final boolean isAdmin() {
    if (currentAuth == null) {
      return false;
    }
    return currentAuth.isAdmin();
  }

  /**
   * Special Authentication for local execution
   *
   * @param hostid
   */
  public final void specialNoSessionAuth(final String hostid) {
    isIdentified = true;
    final SimpleAuth auth =
        new SimpleAuth(hostid, hostid, null, null, 0, null, 0);
    currentAuth = auth;
    setIsIdentified(true);
    user = auth.getUser();
    account = auth.getUser();
    ((FtpSession) getSession()).setSpecialInit(this, new FileBasedDir(
        (FtpSession) getSession()), new FilesystemBasedFtpRestart(
        (FtpSession) getSession()));
    try {
      setBusinessRootFromAuth();
    } catch (final Reply421Exception ignored) {
      // nothing
    }
    getSession().getDir().initAfterIdentification();
    currentAuth.setAdmin(true);
  }

  /**
   * @return the specialId
   */
  public final long getSpecialId() {
    return specialId;
  }

  /**
   * @param specialId the specialId to set
   */
  public final void setSpecialId(final long specialId) {
    this.specialId = specialId;
  }

  /**
   * @return the associated Command Executor
   */
  @Override
  public final CommandExecutor getCommandExecutor() {
    return currentAuth.getCommandExecutor();
  }
}