/**
* This file is part of Graylog.
*
* Graylog 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.
*
* Graylog 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 Graylog. If not, see <http://www.gnu.org/licenses/>.
*/
package org.graylog2.security;
import com.google.common.collect.ImmutableList;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.apache.shiro.realm.Realm;
import org.graylog2.cluster.ClusterConfigChangedEvent;
import org.graylog2.plugin.cluster.ClusterConfigService;
import org.graylog2.utilities.LenientExplicitOrdering;
import javax.annotation.Nonnull;
import javax.inject.Inject;
import java.util.AbstractCollection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
/**
* Runtime (re-)orderable collection of Shiro AuthenticatingRealms.
*
* The generic type is Realm, even though it really only contains AuthenticatingRealms. This is simply to avoid having to
* cast the generic collection when passing it to the SecurityManager.
*/
public class OrderedAuthenticatingRealms extends AbstractCollection<Realm> {
private final Map<String, AuthenticatingRealm> availableRealms;
private final ClusterConfigService clusterConfigService;
private final AtomicReference<List<Realm>> orderedRealms = new AtomicReference<>();
@Inject
public OrderedAuthenticatingRealms(Map<String, AuthenticatingRealm> realms,
ClusterConfigService clusterConfigService,
EventBus eventBus) {
this.availableRealms = realms;
this.clusterConfigService = clusterConfigService;
eventBus.register(this);
sortRealms();
// sortRealms should have produced a reasonable default
Objects.requireNonNull(orderedRealms.get());
}
@Subscribe
public void handleOrderingUpdate(ClusterConfigChangedEvent event) {
if (!AuthenticationConfig.class.getCanonicalName().equals(event.type())) {
return;
}
sortRealms();
}
private void sortRealms() {
final AuthenticationConfig config = clusterConfigService.getOrDefault(AuthenticationConfig.class,
AuthenticationConfig.defaultInstance());
final LenientExplicitOrdering<String> ordering = new LenientExplicitOrdering<>(config.realmOrder());
final ImmutableList<String> newRealmOrder = ordering.immutableSortedCopy(availableRealms.keySet());
orderedRealms.set(newRealmOrder.stream()
.filter(name -> !config.disabledRealms().contains(name))
.map(availableRealms::get)
.collect(Collectors.toList()));
}
@Nonnull
@Override
public Iterator<Realm> iterator() {
return orderedRealms.get().iterator();
}
@Override
public int size() {
return orderedRealms.get().size();
}
}