/*
 * Aipo is a groupware program developed by Aimluck,Inc.
 * Copyright (C) 2004-2011 Aimluck,Inc.
 * http://www.aipo.com
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.aimluck.eip.services.security;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.Date;
import java.util.GregorianCalendar;

import javax.servlet.ServletConfig;

import org.apache.jetspeed.om.security.JetspeedUser;
import org.apache.jetspeed.om.security.UserNamePrincipal;
import org.apache.jetspeed.services.JetspeedSecurity;
import org.apache.jetspeed.services.JetspeedUserManagement;
import org.apache.jetspeed.services.logging.JetspeedLogFactoryService;
import org.apache.jetspeed.services.logging.JetspeedLogger;
import org.apache.jetspeed.services.rundata.JetspeedRunData;
import org.apache.jetspeed.services.rundata.JetspeedRunDataService;
import org.apache.jetspeed.services.security.CredentialExpiredException;
import org.apache.jetspeed.services.security.FailedLoginException;
import org.apache.jetspeed.services.security.JetspeedSecurityCache;
import org.apache.jetspeed.services.security.JetspeedSecurityException;
import org.apache.jetspeed.services.security.JetspeedSecurityService;
import org.apache.jetspeed.services.security.LoginException;
import org.apache.jetspeed.services.security.PortalAuthentication;
import org.apache.jetspeed.services.security.UnknownUserException;
import org.apache.turbine.services.InitializationException;
import org.apache.turbine.services.TurbineBaseService;
import org.apache.turbine.services.TurbineServices;
import org.apache.turbine.services.resources.ResourceService;
import org.apache.turbine.services.rundata.RunDataService;
import org.apache.turbine.util.TurbineException;

import com.aimluck.eip.common.ALBaseUser;
import com.aimluck.eip.common.ALEipConstants;
import com.aimluck.eip.util.ALCellularUtils;
import com.aimluck.eip.util.ALCommonUtils;

/**
 * ライセンス検証のためのクラスです。 <br />
 * 
 */
public class ALEipAuthentication extends TurbineBaseService implements
    PortalAuthentication {

  private static final JetspeedLogger logger =
    JetspeedLogFactoryService.getLogger(ALEipAuthentication.class.getName());

  private JetspeedRunDataService runDataService = null;

  private final static String CONFIG_ANONYMOUS_USER = "user.anonymous";

  String anonymousUser = "anon";

  private final static String CACHING_ENABLE = "caching.enable";

  private boolean cachingEnable = true;

  private final static String CONFIG_PASSWORD_EXPIRATION_PERIOD =
    "password.expiration.period";

  private int expirationPeriod = 0;

  /**
   * 
   * @param username
   * @param cellular_uid
   * @return
   * @throws UnknownUserException
   * @throws JetspeedSecurityException
   */
  public JetspeedUser loginCellularUid(String username, String cellular_uid)
      throws UnknownUserException, JetspeedSecurityException {
    JetspeedUser user =
      JetspeedUserManagement.getUser(new UserNamePrincipal(username));
    ALBaseUser baseUser = (ALBaseUser) user;
    if (cellular_uid == null
      || cellular_uid.length() == 0
      || !cellular_uid.equals(baseUser.getCelluarUId())) {
      logger.error("Invalid cellular uid for user: " + username);
      throw new UnknownUserException(
        "[ALEipAuthentication] Credential authentication failure");
    }

    return user;
  }

  /**
   * 
   */
  @Override
  public JetspeedUser login(String username, String password)
      throws LoginException {

    if (username.equals(this.anonymousUser)) {
      throw new LoginException("Anonymous user cannot login");
    }

    JetspeedUser user = null;

    username = JetspeedSecurity.convertUserName(username);
    password = JetspeedSecurity.convertPassword(password);

    if (password.startsWith(ALEipConstants.KEY_CELLULAR_UID)) {
      // 携帯電話の固有 ID でログイン認証する．

      // TODO by motegi
      // mod by motegi start 携帯での認証でユーザー、パスワード入力時に固有番号も比較するように対応
      String key = getRunData().getParameters().getString("key", "").trim();
      if (key != null && !"".equals(key)) {
        // 簡易ログインの場合は固有番号の比較のみ行なうようにする。
        String cellularUid =
          password.substring(ALEipConstants.KEY_CELLULAR_UID.length(), password
            .length());
        try {
          user = loginCellularUid(username, cellularUid);
        } catch (UnknownUserException e) {
          logger.warn("Unknown user attempted access: " + username, e);
          throw new FailedLoginException(e.toString());
        } catch (JetspeedSecurityException e) {
          logger.warn("User denied authentication: " + username, e);
          throw new LoginException(e.toString());
        }
      } else {
        // 通常ログイン（パスワード入力）の場合にログイン名＋固有番号だけで認証を行なわない
        // ようにする。一旦例外を投げると、パスワードで自動的に再認証される。
        throw new FailedLoginException("");
      }
      // mod end
    } else {
      try {
        user = JetspeedUserManagement.getUser(new UserNamePrincipal(username));
        password = JetspeedSecurity.encryptPassword(password);
      } catch (UnknownUserException e) {
        logger.warn("Unknown user attempted access: " + username, e);
        throw new FailedLoginException(e.toString());
      } catch (JetspeedSecurityException e) {
        logger.warn("User denied authentication: " + username, e);
        throw new LoginException(e.toString());
      }

      if (!user.getPassword().equals(password)) {
        logger.error("Invalid password for user: " + username);
        throw new FailedLoginException("Credential authentication failure");
      }

      // add by motegi
      try {
        JetspeedRunData data = getRunData();

        // JetspeedLink jsLink = JetspeedLinkFactory.getInstance(data);
        // String myUrl =
        // jsLink.getPortletById(
        // data
        // .getParameters()
        // .getString(JetspeedResources.PATH_PORTLETID_KEY)).toString();
        // URL accessUrl = new URL(myUrl);

        URL accessUrl = new URL(data.getRequest().getRequestURL().toString());

        // System.out.println("ポート番号：" + accessUrl.getPort());
        // System.out.println("プロトコル：" + accessUrl.getProtocol());

        // CapabilityMap cm =
        // CapabilityMapFactory.getCapabilityMap(data.getRequest().getHeader(
        // "User-Agent"));
        // MediaTypeEntry media =
        // (MediaTypeEntry) Registry.getEntry(Registry.MEDIA_TYPE, cm
        // .getPreferredMediaType());
        // String mediatype = media.getName();
        // System.out.println("メディアタイプ：" + mediatype);

        if ("https".equals(accessUrl.getProtocol())) {
          final int DEFAULT_PORT1 = -1;
          final int DEFAULT_PORT2 = 443;
          if (accessUrl.getPort() == DEFAULT_PORT1
            || accessUrl.getPort() == DEFAULT_PORT2) {
            if (!ALCellularUtils.isCellularPhone(data)) {
              // httpsのデフォルトポートは携帯用とする。それ以外のアクセスは認証エラーとする。
              logger.error("携帯用ポートに携帯端末以外でアクセスが行なわれました user: " + username);
              throw new FailedLoginException();
            }
          }
        }

        if (ALCellularUtils.isCellularPhone(data)) {
          JetspeedUser user1 =
            JetspeedUserManagement.getUser(new UserNamePrincipal(username));
          ALBaseUser baseUser = (ALBaseUser) user1;
          logger.debug("DBの固有番号：" + baseUser.getCelluarUId());
          String celluid = ALCellularUtils.getCellularUid(data);
          logger.debug("入力の固有番号：" + celluid);

          if (celluid == null
            || baseUser.getCelluarUId() == null
            || celluid.length() == 0
            || baseUser.getCelluarUId().length() == 0) {
            throw new FailedLoginException("");
          }

          if (!celluid.equals(baseUser.getCelluarUId())) {
            throw new FailedLoginException("");
          }
        }
      } catch (JetspeedSecurityException e) {
        logger.error("Invalid password for user: " + username);
        throw new FailedLoginException("Credential authentication failure");
      } catch (MalformedURLException e) {
        logger.error("Invalid URL for user: " + username, e);
        throw new FailedLoginException("Credential authentication failure");
      } catch (TurbineException e) {
        logger.error("Internal Error for user: " + username, e);
        throw new FailedLoginException("Credential authentication failure");
      }
      // add end

    }

    // Check for password expiration
    if (this.expirationPeriod > 0) {
      Date passwordLastChangedDate = user.getPasswordChanged();
      Date passwordExpireDate = null;
      if (passwordLastChangedDate != null) {
        GregorianCalendar gcal =
          (GregorianCalendar) GregorianCalendar.getInstance();
        gcal.setTime(passwordLastChangedDate);
        gcal.add(GregorianCalendar.DATE, this.expirationPeriod);
        passwordExpireDate = gcal.getTime();
        if (logger.isDebugEnabled()) {
          logger.debug("TurbineAuthentication: password last changed = "
            + passwordLastChangedDate.toString()
            + ", password expires = "
            + passwordExpireDate.toString());
        }
      }

      if (passwordExpireDate == null
        || (new Date().getTime() > passwordExpireDate.getTime())) {
        throw new CredentialExpiredException("Password expired");
      }

    }

    // IPA#70075625
    // Sesion Fixation 対策
    JetspeedRunData rundata = getRunData();
    if (rundata != null) {
      // Session ID を再発行する
      rundata.getSession().invalidate();
      rundata.setSession(rundata.getRequest().getSession(true));
    }
    //

    user.setHasLoggedIn(Boolean.valueOf(true));

    try {
      user.updateLastLogin();
      putUserIntoContext(user);
      if (cachingEnable) {
        JetspeedSecurityCache.load(username);
      }
    } catch (Exception e) {
      logger.error("Failed to update last login ", e);
      putUserIntoContext(JetspeedSecurity.getAnonymousUser());
      throw new LoginException("Failed to update last login ", e);
    }

    // for security
    rundata.getUser().setTemp(
      ALEipConstants.SECURE_ID,
      ALCommonUtils.getSecureRandomString());

    return user;
  }

  /**
   * 
   */
  @Override
  public JetspeedUser getAnonymousUser() throws LoginException {
    JetspeedUser user = null;
    try {
      user =
        JetspeedUserManagement.getUser(new UserNamePrincipal(anonymousUser));
      user.setHasLoggedIn(Boolean.valueOf(false));
      putUserIntoContext(user);
      if (cachingEnable) {
        JetspeedSecurityCache.load(user.getUserName());
      }
    } catch (JetspeedSecurityException e) {
      logger.error("Failed to get anonymous user: ", e);
      throw new LoginException("Failed to get anonymous user: ", e);
    }
    return user;
  }

  /**
   * 
   */
  @Override
  public void logout() throws LoginException {
    try {
      getAnonymousUser();
    } catch (Throwable ignore) {
      // ignore
    }
  }

  /**
   * 
   */
  @Override
  public synchronized void init(ServletConfig conf)
      throws InitializationException {
    if (getInit()) {
      return;
    }

    super.init(conf);

    ResourceService serviceConf =
      ((TurbineServices) TurbineServices.getInstance())
        .getResources(JetspeedSecurityService.SERVICE_NAME);

    anonymousUser = serviceConf.getString(CONFIG_ANONYMOUS_USER, anonymousUser);
    cachingEnable = serviceConf.getBoolean(CACHING_ENABLE, cachingEnable);
    expirationPeriod = serviceConf.getInt(CONFIG_PASSWORD_EXPIRATION_PERIOD, 0);

    this.runDataService =
      (JetspeedRunDataService) TurbineServices.getInstance().getService(
        RunDataService.SERVICE_NAME);

    setInit(true);
  }

  protected JetspeedRunData getRunData() {
    JetspeedRunData rundata = null;
    if (this.runDataService != null) {
      rundata = this.runDataService.getCurrentRunData();
    }
    return rundata;
  }

  protected JetspeedUser getUserFromContext() {
    JetspeedRunData rundata = getRunData();
    JetspeedUser user = null;
    if (rundata != null) {
      user = (JetspeedUser) rundata.getUser();
    }
    return user;
  }

  protected JetspeedRunData putUserIntoContext(JetspeedUser user) {
    JetspeedRunData rundata = getRunData();
    if (rundata != null) {

      rundata.setUser(user);
      rundata.save();
    }
    return rundata;
  }
}
