tech.beshu.ror.acl.blocks.rules.impl.GroupsAsyncRule.java Source code

Java tutorial

Introduction

Here is the source code for tech.beshu.ror.acl.blocks.rules.impl.GroupsAsyncRule.java

Source

/*
 *    This file is part of ReadonlyREST.
 *
 *    ReadonlyREST 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.
 *
 *    ReadonlyREST 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 ReadonlyREST.  If not, see http://www.gnu.org/licenses/
 */

package tech.beshu.ror.acl.blocks.rules.impl;

import com.google.common.base.Strings;
import com.google.common.collect.Sets;
import tech.beshu.ror.acl.blocks.rules.AsyncRule;
import tech.beshu.ror.acl.blocks.rules.RuleExitResult;
import tech.beshu.ror.acl.blocks.rules.phantomtypes.Authentication;
import tech.beshu.ror.acl.blocks.rules.phantomtypes.Authorization;
import tech.beshu.ror.acl.definitions.users.User;
import tech.beshu.ror.acl.definitions.users.UserFactory;
import tech.beshu.ror.commons.domain.LoggedUser;
import tech.beshu.ror.requestcontext.__old_RequestContext;
import tech.beshu.ror.settings.definitions.UserSettings;
import tech.beshu.ror.settings.rules.GroupsRuleSettings;
import tech.beshu.ror.utils.FuturesSequencer;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * A GroupsSyncRule checks if a request containing Basic Authentication credentials
 * matches a user in one of the specified groups.
 *
 * @author Christian Henke (maitai@users.noreply.github.com)
 */
public class GroupsAsyncRule extends AsyncRule implements Authorization, Authentication {

    private final GroupsRuleSettings settings;
    private final Map<String, User> users;

    public GroupsAsyncRule(GroupsRuleSettings s, UserFactory userFactory) {
        this.settings = s;
        this.users = settings.getUsersSettings().stream().map(uSettings -> userFactory.getUser(uSettings))
                .collect(Collectors.toMap(User::getUsername, Function.identity()));
    }

    @Override
    public CompletableFuture<RuleExitResult> match(__old_RequestContext rc) {

        // All configured groups in a block's group rule, contextualized
        Set<String> resolvedGroups = settings.getGroups().stream().map(g -> g.getValue(rc))
                .filter(Optional::isPresent).map(Optional::get).collect(Collectors.toSet());

        List<UserSettings> userSettingsToCheck = settings.getUsersSettings();

        // Filter the userSettings to leave only the ones which include the current group
        // #TODO move to proper use of optional
        final String preferredGroup = rc.getLoggedInUser().isPresent()
                ? rc.getLoggedInUser().get().resolveCurrentGroup(rc.getHeaders()).orElse(null)
                : new LoggedUser("x").resolveCurrentGroup(rc.getHeaders()).orElse(null);

        // Exclude userSettings that don't contain groups in this groupRule
        userSettingsToCheck = userSettingsToCheck.stream()
                .filter(us -> !Sets.intersection(us.getGroups(), resolvedGroups).isEmpty())
                .collect(Collectors.toList());

        if (!Strings.isNullOrEmpty(preferredGroup)) {
            if (!resolvedGroups.contains(preferredGroup)) {
                return CompletableFuture.completedFuture(NO_MATCH);
            }
        }

        if (userSettingsToCheck.isEmpty()) {
            return CompletableFuture.completedFuture(NO_MATCH);
        }

        // Check remaining userSettings for matching auth
        return FuturesSequencer.runInSeqUntilConditionIsUndone(

                // Iterator of users which match at least one group in this block's group rule
                userSettingsToCheck.iterator(),

                // Asynchronously map for each userSetting, return MATCH when we authenticated the first user
                uSettings -> {
                    if (!Strings.isNullOrEmpty(preferredGroup) && !uSettings.getGroups().contains(preferredGroup)) {
                        return CompletableFuture.completedFuture(NO_MATCH);
                    }
                    return users.get(uSettings.getUsername()).getAuthKeyRule().match(rc).exceptionally(e -> {
                        e.printStackTrace();
                        return NO_MATCH;
                    }).thenApply(rExitRes -> {
                        if (!rExitRes.isMatch()) {
                            return rExitRes;
                        }
                        if (!rc.getLoggedInUser().filter(lu -> uSettings.getUsername().equals(lu.getId()))
                                .isPresent()) {
                            // User is authenticated, but was not declared as "username"
                            return NO_MATCH;
                        }
                        return rExitRes;
                    });
                },

                // Boolean decision (true = break loop)
                (uSettings, ruleExit) -> {

                    if (!ruleExit.isMatch() && rc.getLoggedInUser().isPresent()) {
                        return false;
                    }

                    // Now that we have a user, and we know this is a match, we can populate the headers.
                    if (ruleExit.isMatch()) {
                        rc.getLoggedInUser().ifPresent(lu -> {
                            User u = users.get(lu.getId());
                            if (u != null) {
                                lu.addAvailableGroups(u.getGroups());
                                Optional<String> cu = lu.resolveCurrentGroup(rc.getHeaders());
                                if (!cu.isPresent() && ruleExit.isMatch()) {
                                    lu.setCurrentGroup(resolvedGroups.iterator().next());
                                }
                            }
                        });
                    }
                    return ruleExit.isMatch();
                },

                // If never true..
                nothing -> NO_MATCH).exceptionally(e -> {
                    e.printStackTrace();
                    throw new CompletionException(e);
                });

    }

    @Override
    public String getKey() {
        return settings.getName();
    }
}