*/ require_once ("domframework/auth.php"); require_once ("domframework/authparams.php"); require_once ("domframework/authsession.php"); require_once ("domframework/ratelimit.php"); require_once ("domframework/ratelimitfile.php"); /** All the authentication protocol */ class authentication { /** The email of the authenticated/nonauthenticated user */ //private $email = "anonymous"; /** The debug of the authentication methods */ public $debug = 0; /** The route object */ private $route; /** Number of authentication maximum by minute */ public $ratelimitAuth = 3; /** Directory to store the ratelimit files */ public $ratelimitDir = "/tmp"; /** The rest authentication methods. Can be http, session, post. Attention : session case = CSRF ! */ public $restMethods = array ("http"); /** The html authentication methods. Can be http, session, post */ public $htmlMethods = array ("session"); /** The authentication methods. Can be ldap, sympa...*/ public $authMethods = array (); /** The authentication servers configuration array ("authXXXX"=>array ( array ("ldapserver"=>"ldaps://annuaire.grenoble.cnrs.fr", "ldapport"=>636, "ldaptimeout"=>5, "ldapauth"=>"uid=annuaire,ou=people,dc=grenoble,dc=cnrs,dc=fr", "ldappwd"=>";authANNUAIRE2013", "ldapbase"=>"", "ldapfilter"=>"(mail=%s)", "ldapfield"=>"mail", "ldapfiltersearch"=>"(objectClass=inetOrgPerson)" ), ), );*/ public $authServers = array (); /** The application Name displayed on authentication page */ public $appName = null; /** The class and method to use to log the errors */ public $loggingFunc; public function __construct ($route) { $this->route = $route; $this->loggingFunc = array ($this, "logging"); } /* public function email () { return $this->email; }*/ /** Disconnect the user */ public function logout () { if ($this->debug) echo "
LOGOUT\n";
    $authsession = new \authsession ();
    $param = $authsession->getdetails ();
    if ($this->debug) echo "Logout for '".$param["email"]."'\n";
    call_user_func ($this->loggingFunc,
                    LOG_NOTICE,
                    "Logout for '".$param["email"]."'");
    $authsession->logout ();
    unset ($_SESSION["domframework"]["authentication"]);
    $_SESSION["domframework"]["authentication"]["message"] =
       dgettext("domframework", "You have been logged out");
    if ($this->debug) echo "Redirect to authentication page";
    if ($this->debug) $this->route->debug = $this->debug;
    $this->route->redirect ("/authentication", "");
  }

  /** Display the login page */
  public function pageHTML ($url = "")
  {
    // If the user is already connected, redirect to the main page of the site
    $auth = new \auth ();
    $pre = new \authparams (array ("session"));
    if (isset ($_SESSION["domframework"]["authentication"]["message"]))
      $message = $_SESSION["domframework"]["authentication"]["message"];
    else
      $message = "";
    unset ($_SESSION["domframework"]["authentication"]["message"]);
    $alreadyAuth = false;
    if ($pre->email !== "anonymous")
      $alreadyAuth = $pre->email;
    if ($this->appName !== null)
      $auth->appName = $this->appName;
    echo $auth->pageHTML ($this->route->baseURL(), $message, $url,
                          $alreadyAuth);
  }

  /** Check the authentication page */
  public function verifAuthLoginPage ($url = "")
  {
    // rate-limit the connections
    $ratelimiter = new \ratelimitfile ();
    // 3 connections by minutes
    $ratelimiter->maxEntries = $this->ratelimitAuth;
    $ratelimiter->storageDir = $this->ratelimitDir;
    if (isset ($_SERVER["HTTP_X_FORWARDED_FOR"]))
      $ipClient = $_SERVER["HTTP_X_FORWARDED_FOR"];
    else
      $ipClient = $_SERVER["REMOTE_ADDR"];
    if ($ratelimiter->set ("loggin-$ipClient") === false)
    {
      call_user_func ($this->loggingFunc,
                      LOG_WARNING,
                      "Ratelimiting for $ipClient");
      $_SESSION["domframework"]["authentication"]["message"] =
        dgettext("domframework", "Too much connections");
      if ($url === "")
      {
        $this->route->redirect ("/authentication", "");
      }
      else
      {
        $this->route->redirect ("/authentication/$url", "");
      }
    }
    $authparams = new \authparams (array ("post"));
    $res = $this->verifAuth ($authparams->email, $authparams->password);
    if (! is_array ($res))
    {
      // Authentication error
      // Redirect to login page after logout
      call_user_func ($this->loggingFunc,
                      LOG_WARNING,
                      "Logging error for '$authparams->email' (HTML) : $res");
      $authsession = new \authsession ();
      $authsession->logout ();
      $baseURL = $this->route->baseURL ();
      $_SESSION["domframework"]["authentication"]["message"] = $res;
      // Check the provided URL before using it
      if ($url === "")
      {
        $this->route->redirect ("/authentication", "");
      }
      else
      {
        $this->route->redirect ("/authentication/$url", "");
      }
    }
    // Login OK : save in SESSION and go to main page
    call_user_func ($this->loggingFunc,
                    LOG_NOTICE,
                    "Logging in for '$authparams->email'");
    $session = new \authsession ();
    $session-> savedata ($authparams->email, $authparams->password,
                          $res["lastname"], $res["firstname"]);
    if ($url === "")
      $this->route->redirect ("/", "");
    else
      $this->route->redirect ("/$url", "");
  }

  /** Check all the REST API */
  public function verifAuthREST ()
  {
    if ($this->debug)
      echo "=== entering verifAuthREST (restMethods=".
        print_r ($this->restMethods, true).")\n";
    $authparams = new \authparams ($this->restMethods);
    $res = array ("email"=>"anonymous", "password"=>"anonymous");
    if ($authparams->email !== "anonymous" &&
        $authparams->password !== "anonymous")
    {
      $res = $this->verifAuth ($authparams->email, $authparams->password);
    }
    if (! is_array ($res))
    {
      call_user_func ($this->loggingFunc,
                      LOG_WARNING,
                      "Logging error for '$authparams->email' (REST) : $res");
      // Authentication error
      throw new \Exception (dgettext ("domframework",
                                      "Authentication error"), 403);
    }
    return $res;
  }

  /** Check all the others pages of the site */
  public function verifAuthHTML ()
  {
    if ($this->debug)
      echo "=== entering verifAuthHTML (htmlMethods=".
        print_r ($this->htmlMethods, true).")\n";
    $authparams = new \authparams ($this->htmlMethods);
    // Don't ask to the provider if anonymous is known
    if ($authparams->email === "anonymous" || $authparams->email === null)
    {
      if ($this->debug) echo "Anonymous\n";
      return array ("email"=>"anonymous", "lastname"=>"Anonymous",
                    "firstname"=>"", "password"=>"");
    }
    if ($this->debug)
      echo "verifAuth ($authparams->email, $authparams->password)\n";
    $res = $this->verifAuth ($authparams->email, $authparams->password);
    if (! is_array ($res))
    {
      // Authentication error
      if ($this->debug) echo "Previous session not found";
      $msg = dgettext("domframework", "Previous session not found");
      $_SESSION["domframework"]["authentication"]["message"] = $msg;
      call_user_func ($this->loggingFunc,
                      LOG_WARNING,
                      "Previous session not found for '$authparams->email'");
      $url = $this->route->requestURL();
      $this->route->redirect ("/authentication/$url");
    }
    return $res;
  }

  /** Do the real authentication process on all the providers defined in the
      properties of the class.
      @return an array containing the user data if the authentication is
              correct,
              an exception if noting is found */
  private function verifAuth ($email, $password)
  {
    if ($this->debug)
      echo "Entering in verifAuth ($email, xxxxxxxx)\n";
    if (! is_array ($this->authMethods) || count ($this->authMethods) === 0)
      throw new \Exception ("No authentication method defined", 500);
    if ($this->debug < 2 &&
        isset ($_SESSION["domframework"]["authentication"]["lastcheck"]) &&
        $_SESSION["domframework"]["authentication"]["lastcheck"] + 180 <
          time ())
    {
      // Test the authentication each 3 minutes if there is a session, else
      // return the previous values
      if ($this->debug)
        echo "verifAuth : using auth cache (push in debug=2 to skip)\n";
      return $_SESSION["domframework"]["authentication"]["authcache"];
    }

    foreach ($this->authMethods as $method)
    {
      if ($this->debug)
        echo "Authentication method=$method\n";
      if (! is_string ($method))
        throw new \Exception ("The authentication method is not a string", 500);
      $classname = "auth$method";
      require_once ("domframework/$classname.php");
      if (! array_key_exists ($classname, $this->authServers))
        throw new \Exception ("No authentication server '$classname' enabled",
                             500);
      // If only one server is defined, the parameters can directely be pushed
      // to the classname
      if (! is_array (reset ($this->authServers[$classname])))
      {
        $this->authServers[$classname] = array ($this->authServers[$classname]);
      }
      if ($this->debug >= 2)
        echo "Authentication method=$method : authServers=".
          var_export ($this->authServers[$classname])."\n";
      if (! is_array ($this->authServers[$classname]) ||
          count ($this->authServers[$classname]) === 0)
        throw new \Exception ("No authentication server defined for method ".
                             "'$method'", 500);
      foreach ($this->authServers[$classname] as $key=>$serversParam)
      {
        if ($this->debug)
          echo "Test auth server $method # $classname # $key\n";
        if (! is_array ($serversParam))
          throw new \Exception ("Auth Server $key configuration error : ".
                               "not an array", 500);
        $authmethod = new $classname ();
        foreach ($serversParam as $param=>$value)
        {
          if ($this->debug)
            echo "Add param '$param' with value '$value' to auth$method\n";
          $authmethod->$param = $value;
        }
        $authmethod->connect ();
        try
        {
          $authmethod->authentication ($email, $password);
          $_SESSION["domframework"]["authentication"]["authcache"] =
            $authmethod->getdetails ();
          $_SESSION["domframework"]["authentication"]["lastcheck"] = time ();
          return $authmethod->getdetails ();
        }
        catch (\Exception $e)
        {
          call_user_func ($this->loggingFunc,
                          LOG_DEBUG,
                          "Authentication error for '$email' : ".
                          "$classname : ".$e->getMessage());
        }
      }
    }
    return dgettext("domframework", "Bad login/password");
  }

  /** Add the authentication routes to the routing model for HTML
    * authentication. Not needed if using shibboleth, HTTP auth...
    */
  public function routes ()
  {
    $authObj = $this;
    $this->route->get ("authentication/logout", function () use ($authObj)
    {
      $authObj->logout ();
    });

    $this->route->get ("authentication({url})?", function ($url) use ($authObj)
    {
      $authObj->pageHTML ($url);
      exit;
    });

    $this->route->post ("authentication({url})?", function ($url) use ($authObj)
    {
      $authObj->verifAuthLoginPage ($url);
      exit;
    });
    $this->route->authenticationURL = "/authentication";
  }

  /** The default method to display the error messages.
    * Do not display the debug messages, and write the errors on screen
    */
  private function logging ($priority, $message)
  {
    if ($priority > 4)
      return;
    echo "$priority : $message\n";
  }
}