All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jgrapes.webconsole.rbac.RoleConfigurator Maven / Gradle / Ivy

There is a newer version: 1.5.1
Show newest version
/*
 * JGrapes Event Driven Framework
 * Copyright (C) 2022 Michael N. Lipp
 * 
 * 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 .
 */

package org.jgrapes.webconsole.rbac;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.security.auth.Subject;
import org.jgrapes.core.Channel;
import org.jgrapes.core.Component;
import org.jgrapes.core.Event;
import org.jgrapes.core.Manager;
import org.jgrapes.core.annotation.Handler;
import org.jgrapes.util.events.ConfigurationUpdate;
import org.jgrapes.webconsole.base.ConsoleRole;
import org.jgrapes.webconsole.base.ConsoleUser;
import org.jgrapes.webconsole.base.events.UserAuthenticated;

/**
 * Configures roles (of type {@link ConsoleRole)} 
 * for the user currently logged in.
 */
public class RoleConfigurator extends Component {

    @SuppressWarnings("PMD.UseConcurrentHashMap")
    private final Map> roles = new HashMap<>();
    private boolean replace;

    /**
     * Creates a new component with its channel set to the given 
     * channel.
     *
     * @param componentChannel the channel that the component's
     * handlers listen on by default and that 
     * {@link Manager#fire(Event, Channel...)} sends the event to
     */
    public RoleConfigurator(Channel componentChannel) {
        super(componentChannel);
    }

    /**
     * Creates a new component with its channel set to the given 
     * channel.
     *
     * Supported properties are:
     * 
     *  * *rolesByUser*: see {@link #setRolesByUser(Map)}.
     *
     * @param componentChannel the channel that the component's
     * handlers listen on by default and that 
     * {@link Manager#fire(Event, Channel...)} sends the event to
     * @param properties the properties used to configure the component
     */
    @SuppressWarnings({ "unchecked", "PMD.ConstructorCallsOverridableMethod" })
    public RoleConfigurator(Channel componentChannel,
            Map properties) {
        super(componentChannel);
        setRolesByUser((Map>) properties
            .get("rolesByUser"));
    }

    /**
     * Sets the roles associated with a user. The parameter
     * is a Map> holding the roles to be  
     * associated with a given user. The special key "*" may
     * be used to specify roles that are to be added to any user.
     *
     * @param roles the roles
     * @return the user role conlet filter
     */
    @SuppressWarnings({ "PMD.LinguisticNaming",
        "PMD.AvoidInstantiatingObjectsInLoops" })
    public RoleConfigurator setRolesByUser(Map> roles) {
        this.roles.clear();
        this.roles.putAll(roles);
        for (var e : this.roles.entrySet()) {
            e.setValue(new HashSet<>(e.getValue()));
        }
        return this;
    }

    /**
     * Control whether the component replaces all {@link ConsoleRole}s
     * or adds to the existing roles (default).
     *
     * @param replace the replace
     * @return the role configurator
     */
    @SuppressWarnings("PMD.LinguisticNaming")
    public RoleConfigurator setReplace(boolean replace) {
        this.replace = replace;
        return this;
    }

    /**
     * The component can be configured with events that include
     * a path (see @link {@link ConfigurationUpdate#paths()})
     * that matches this components path (see {@link Manager#componentPath()}).
     * 
     * The following properties are recognized:
     * 
     * `rolesByUser`
     * : Invokes {@link #setRolesByUser(Map)} with the given values.
     *
     * `replace`
     * : Invokes {@link #setReplace(boolean)} with the given value.
     * 
     * @param event the event
     */
    @SuppressWarnings("unchecked")
    @Handler
    public void onConfigUpdate(ConfigurationUpdate event) {
        event.structured(componentPath())
            .map(c -> (Map>) c.get("rolesByUser"))
            .map(m -> m.entrySet().stream()
                .collect(Collectors.toMap(Map.Entry::getKey,
                    e -> e.getValue().stream().collect(Collectors.toSet()))))
            .ifPresent(this::setRolesByUser);
        event.value(componentPath(), "replace").map(Boolean::valueOf)
            .ifPresent(this::setReplace);
    }

    /**
     * Sets the roles in the subject with the authenticated user.
     *
     * @param event the event
     * @param channel the channel
     */
    @Handler(priority = 900)
    public void onUserAuthenticated(UserAuthenticated event, Channel channel) {
        Subject subject = event.subject();
        if (replace) {
            for (var itr = subject.getPrincipals().iterator(); itr.hasNext();) {
                if (itr.next() instanceof ConsoleRole) {
                    itr.remove();
                }
            }
        }
        Stream.concat(
            subject.getPrincipals(ConsoleUser.class).stream()
                .findFirst().map(ConsoleUser::getName).stream(),
            Stream.of("*")).map(roles::get).filter(Objects::nonNull)
            .flatMap(Set::stream).map(ConsoleRole::new)
            .forEach(p -> subject.getPrincipals().add(p));
        event.by("Role Configurator");
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy