# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.

package Bugzilla::Auth::Login::Stack;

use 5.10.1;
use strict;
use warnings;

use base qw(Bugzilla::Auth::Login);
use fields qw(
  _stack
  successful
);
use Hash::Util qw(lock_keys);
use Bugzilla::Hook;
use Bugzilla::Constants;
use List::MoreUtils qw(any);

sub new {
  my $class   = shift;
  my $self    = $class->SUPER::new(@_);
  my $list    = shift;
  my %methods = map { $_ => "Bugzilla/Auth/Login/$_.pm" } split(',', $list);
  lock_keys(%methods);
  Bugzilla::Hook::process('auth_login_methods', {modules => \%methods});

  $self->{_stack} = [];
  foreach my $login_method (split(',', $list)) {
    my $module = $methods{$login_method};
    require $module;
    $module =~ s|/|::|g;
    $module =~ s/.pm$//;
    push(@{$self->{_stack}}, $module->new(@_));
  }
  return $self;
}

sub get_login_info {
  my $self = shift;
  my $result;
  foreach my $object (@{$self->{_stack}}) {

    # See Bugzilla::WebService::Server::JSONRPC for where and why
    # auth_no_automatic_login is used.
    if (Bugzilla->request_cache->{auth_no_automatic_login}) {
      next if $object->is_automatic;
    }
    $result = $object->get_login_info(@_);
    $self->{successful} = $object;

    # We only carry on down the stack if this method denied all knowledge.
    last
      unless ($result->{failure}
      && ( $result->{failure} eq AUTH_NODATA
        || $result->{failure} eq AUTH_NO_SUCH_USER));

    # If none of the methods succeed, it's undef.
    $self->{successful} = undef;
  }
  return $result;
}

sub fail_nodata {
  my $self = shift;

  # We fail from the bottom of the stack.
  my @reverse_stack = reverse @{$self->{_stack}};
  foreach my $object (@reverse_stack) {

    # We pick the first object that actually has the method
    # implemented.
    if ($object->can('fail_nodata')) {
      $object->fail_nodata(@_);
    }
  }
}

sub can_login {
  my ($self) = @_;

  # We return true if any method can log in.
  foreach my $object (@{$self->{_stack}}) {
    return 1 if $object->can_login;
  }
  return 0;
}

sub user_can_create_account {
  my ($self) = @_;

  # We return true if any method allows users to create accounts.
  foreach my $object (@{$self->{_stack}}) {
    return 1 if $object->user_can_create_account;
  }
  return 0;
}

sub extern_id_used {
  my ($self) = @_;
  return any { $_->extern_id_used } @{$self->{_stack}};
}

1;
