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

com.rapidclipse.framework.security.authorization.Role Maven / Gradle / Ivy

There is a newer version: 14.0.0
Show newest version
/*
 * Copyright (C) 2013-2023 by XDEV Software, All Rights Reserved.
 *
 * This file is part of the RapidClipse Application Platform (RAP).
 *
 * 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 .
 *
 * SPDX-License-Identifier: AGPL-3.0-or-later
 *
 * Contributors:
 *     XDEV Software - initial API and implementation
 */
package com.rapidclipse.framework.security.authorization;

import static java.util.Objects.requireNonNull;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;


/**
 * The type representing a role for the purpose of authorization management. A
 * role is defined as a collection of permissions and other roles whose
 * permissions are applied recursively to it.
 *
 * Application-specific types that shall be handled by the authorization
 * framework have to implement this interface.
 *
 * @author XDEV Software (TM)
 */
public interface Role
{
	/**
	 * @return The role's unchangable name that identifies a particular
	 *         instance.
	 */
	public String name();

	/**
	 * @return The roles this role instance has been defined by.
	 */
	public Collection roles();

	/**
	 * @return The permissions this role instance has been defined by.
	 */
	public Collection permissions();

	///////////////////////////////////////////////////////////////////////////
	// default methods //
	/////////////////////

	/**
	 * Returns the effective roles of this {@link Role} instance, meaning a
	 * collection containing all the roles that this instance has been defined
	 * by and all their effective roles. Every {@link Role} instance is only
	 * returned once (identity uniqueness).
	 *
	 * @return the effective roles.
	 */
	public default Set effectiveRoles()
	{
		return collectEffectiveRoles(this.roles(), new HashSet<>());
	}

	/**
	 * Returns the effective permissions of this {@link Role} instance, meaning
	 * a collection containing all the permissions that this instance has been
	 * defined by and the effective permissions of all its roles. For every
	 * unique {@link Resource} instance the permission with the highest absolute
	 * value is returned.
	 *
	 * @return the effective permissions.
	 */
	public default Map effectivePermissions()
	{
		return collectEffectivePermissions(new HashSet<>(Arrays.asList(this)));
	}

	///////////////////////////////////////////////////////////////////////////
	// static methods //
	///////////////////

	/**
	 * Utility method that collects the effective permissions from a collection
	 * of passed roles.
	 *
	 * @param roles
	 *            the {@link Role} instances whose effective permissions shall
	 *            be determined.
	 * @return the passed roles' collected effective permissions.
	 * @see #effectivePermissions()
	 */
	public static Map collectEffectivePermissions(
		final Collection roles)
	{
		return collectEffectivePermissions(roles, new HashSet<>(), new HashSet<>(), new HashSet<>(),
			new HashMap<>());
	}

	public static > M collectEffectivePermissions(
		final Collection roles,
		final Set handledRoles,
		final Set handledPermissions,
		final Set handledResources,
		final M collectedPermissions)
	{
		for(final Role role : roles)
		{
			if(handledRoles.add(role))
			{
				collectEffectivePermissions(role.permissions(), handledPermissions, handledResources,
					collectedPermissions);
				collectEffectivePermissions(role.roles(), handledRoles, handledPermissions,
					handledResources, collectedPermissions);
			}
		}

		return collectedPermissions;
	}

	public static > C collectEffectiveRoles(
		final Collection roles,
		final C effectiveRoles)
	{
		// more specific roles take precedence, so it's desired to use add
		// instead of put
		/*
		 * (19.06.2015 TM)NOTE: might be destroyed be using old JDK collections:
		 * There is no Set type with proper order (like "Enum" in Jadoth Core
		 * library) List would allow duplicates which is false, Set has no
		 * order. JDK collections are just not sufficient for proper software
		 * development.
		 */
		for(final Role role : roles)
		{
			if(effectiveRoles.add(role))
			{
				// for every newly added role (not contained yet) collect it's
				// roles recursively
				collectEffectiveRoles(roles, effectiveRoles);
			}
		}

		return effectiveRoles;
	}

	public static void collectEffectivePermissions(
		final Iterable permissions,
		final Set handledPermissions,
		final Set handledResources,
		final Map effectivePermissions)
	{
		for(final Permission permission : permissions)
		{
			if(!handledPermissions.add(permission))
			{
				continue; // this permission (identity) has already been handled
							// via another role, so skip it
			}

			final Resource resource = permission.resource();

			/*
			 * must clear the handledResources collection for every unique
			 * permission as the current permission might handle a previously
			 * handled resource again with potentially higher factor and
			 * therefor required update to effectivePermissions. The
			 * handledResources is still necessary to recognize recursive
			 * definitions.
			 */
			handledResources.clear();
			collectEffectivePermissions(resource, permission, handledResources, effectivePermissions);
		}
	}

	public static void collectEffectivePermissions(
		final Resource resource,
		final Permission permission,
		final Set handledResources,
		final Map effectivePermissions)
	{
		if(!handledResources.add(resource))
		{
			return; // already handled
		}

		final Permission collectedPermission = effectivePermissions.get(resource);
		if(collectedPermission == null
			|| Math.abs(collectedPermission.factor()) < Math.abs(permission.factor()))
		{
			effectivePermissions.put(resource, permission);
		}

		for(final Resource child : resource.children())
		{
			collectEffectivePermissions(child, permission, handledResources, effectivePermissions);
		}
	}

	///////////////////////////////////////////////////////////////////////////
	// constructors //
	/////////////////

	/**
	 * Returns a new {@link Role} instance defined by the passed values.
	 *
	 * @param name
	 *            the unchangeable identifying name of the role.
	 * @param roles
	 *            the parent roles of the new role, potentially none.
	 * @param permissions
	 *            the explicitely granted permissions for the new role,
	 *            potentially none.
	 * @return a new {@link Role} instance.
	 */
	public static Role New(
		final String name,
		final Set roles,
		final Set permissions)
	{
		return new Role.Default(name, roles, permissions);
	}

	/**
	 * Returns a new {@link Role} instance defined by the passed values with no
	 * explicit permissions.
	 *
	 * @param name
	 *            the unchangeable identifying name of the role.
	 * @param roles
	 *            the parent roles of the new role, potentially none.
	 * @return a new {@link Role} instance.
	 */
	public static Role NewFromRoles(final String name, final Set roles)
	{
		return new Role.Default(name, roles, Collections.emptySet());
	}

	/**
	 * Returns a new {@link Role} instance defined by the passed values with no
	 * explicit parent roles.
	 *
	 * @param name
	 *            the unchangeable identifying name of the role.
	 * @param permissions
	 *            the explicitely granted permissions for the new role,
	 *            potentially none.
	 * @return a new {@link Role} instance.
	 */
	public static Role NewFromPermissions(
		final String name,
		final Set permissions)
	{
		return new Role.Default(name, Collections.emptySet(), permissions);
	}

	/**
	 * Returns a new {@link Role} instance with the passed name that is
	 * otherwise empty.
	 *
	 * @param name
	 *            the unchangeable identifying name of the role.
	 * @return a new {@link Role} instance.
	 */
	public static Role New(final String name)
	{
		return new Role.Default(name, Collections.emptySet(), Collections.emptySet());
	}

	/**
	 * Returns a new {@link Role} instance defined by the passed values with no
	 * explicit permissions.
	 *
	 * @param name
	 *            the unchangeable identifying name of the role.
	 * @param roles
	 *            the parent roles of the new role, potentially none.
	 * @return a new {@link Role} instance.
	 */
	public static Role New(final String name, final Role... roles)
	{
		return new Role.Default(name,
			roles == null ? Collections.emptySet() : new HashSet<>(Arrays.asList(roles)),
			Collections.emptySet());
	}

	/**
	 * Returns a new {@link Role} instance defined by the passed values with no
	 * explicit parent roles.
	 *
	 * @param name
	 *            the unchangeable identifying name of the role.
	 * @param permissions
	 *            the explicitely granted permissions for the new role,
	 *            potentially none.
	 * @return a new {@link Role} instance.
	 */
	public static Role New(final String name, final Permission... permissions)
	{
		return new Role.Default(name, Collections.emptySet(),
			permissions == null ? Collections.emptySet()
				: new HashSet<>(Arrays.asList(permissions)));
	}

	/**
	 * Validates and returns a passed non-null {@link Role} instance or creates
	 * a new one in case of a null refrence. In this simple defeault
	 * implementation, validating simply means to check if the passed role's
	 * name is equal to the passed name.
	 *
	 * @param role
	 *            an already existing {@link Role} instance for that name or
	 *            null.
	 * @param name
	 *            the indentifying name of the role. May not be null.
	 * @param parentRoles
	 *            the names of the parent roles as per definition.
	 * @param permissions
	 *            the names of the explicit permissions as per definition.
	 * @return the non-null passed {@link Role} instance, otherwise a new one.
	 */
	public static Role provide(
		final Role role,
		final String name,
		final Set parentRoles,
		final Set permissions)
	{
		if(name == null)
		{
			// (25.06.2014 TM)TODO: all plain string exceptions should be
			// refactored to proper types
			throw new IllegalArgumentException("No role name given.");
		}

		if(role != null)
		{
			if(!name.equals(role.name()))
			{
				throw new IllegalArgumentException("Invalid name for existing role: " + name);
			}

			// (25.06.2014 TM)TODO: validate parent roles and permissions by
			// name
			return role;
		}

		// instantiate only with name, roles and permissions get updated later
		return New(name);
	}

	/**
	 * Updates the passed {@link Role} instance for the given values. If
	 * inconsistencies are detected or the instance is not {@link Mutable}, an
	 * {@link IllegalArgumentException} is thrown.
	 *
	 * @param role
	 *            the {@link Role} instance to be updated.
	 * @param name
	 *            the identifiying name of the role.
	 * @param parentRoles
	 *            the parent roles that the passed {@link Role} instance shall
	 *            be updated with.
	 * @param permissions
	 *            the explicit permissions that the passed {@link Role} instance
	 *            shall be updated with.
	 * @throws IllegalArgumentException
	 *             if name is null, names do not match or the role is not
	 *             {@link Mutable}.
	 */
	public static void update(
		final Role role,
		final String name,
		final Set parentRoles,
		final Set permissions)
	{
		if(name == null)
		{
			throw new IllegalArgumentException("No role name given.");
		}

		if(!name.equals(role.name()))
		{
			throw new IllegalArgumentException("Invalid name for role: " + name);
		}

		if(!(role instanceof Mutable))
		{
			throw new IllegalArgumentException(
				"Passed subject is not of a generically mutable type");
		}

		// ensure that all updating methods are called atomically
		synchronized(role)
		{
			((Mutable)role).setRoles(parentRoles);
			((Mutable)role).setPermissions(permissions);
		}
	}

	///////////////////////////////////////////////////////////////////////////
	// member types //
	/////////////////////

	/**
	 * Interface equivalent of a constructor for the interface type {@link Role}
	 * .
	 *
	 * @author XDEV Software (TM)
	 */
	@FunctionalInterface
	public interface Creator
	{
		public Role createRole(
			String name,
			Set roles,
			Set permissions);
	}

	/**
	 * Extension of the type {@link Role} with mutability methods. This concept
	 * is useful to have a properly typed representation of optional mutability.
	 *
	 * @author XDEV Software (TM)
	 */
	public interface Mutable extends Role
	{
		/**
		 * Sets the passed {@link Permission} instances as the new explicit
		 * permissions for this {@link Role} instance.
		 *
		 * @param permissions
		 *            the {@link Permission} instances to be set.
		 */
		public void setPermissions(Collection permissions);

		/**
		 * Sets the passed {@link Role} instances as the new parent roles for
		 * this {@link Role} instance.
		 *
		 * @param roles
		 *            the {@link Role} instances to be set.
		 */
		public void setRoles(Collection roles);
	}

	/**
	 * A simple {@link Role.Mutable} default implementation.
	 *
	 * @author XDEV Software (TM)
	 */
	public class Default implements Role.Mutable
	{
		///////////////////////////////////////////////////////////////////////////
		// instance fields //
		////////////////////

		private final String                    name;
		private volatile Collection       roles;
		private volatile Collection permissions;

		///////////////////////////////////////////////////////////////////////////
		// constructors //
		/////////////////

		/**
		 * Implementation detail constructor that might change in the future.
		 */
		protected Default(
			final String name,
			final Collection roles,
			final Collection permissions)
		{
			super();
			this.name = requireNonNull(name);
			this.setRoles(roles);
			this.setPermissions(permissions);

			/*
			 * note: cannot cache effective items immediately in unupdatable /
			 * immutable implementation as the referenced roles might not be
			 * complete yet.
			 */
		}

		/**
		 * Sets the passed {@link Permission} instances in a internally newly
		 * instantiated collection.
		 *
		 * @param permissions
		 *            the {@link Permission} instances to be set.
		 */
		@Override
		public void setPermissions(final Collection permissions)
		{
			this.permissions = permissions == null ? Collections.emptySet()
				: new HashSet<>(permissions);
		}

		/**
		 * Sets the passed {@link Role} instances in a internally newly
		 * instantiated collection.
		 *
		 * @param roles
		 *            the {@link Role} instances to be set.
		 */
		@Override
		public void setRoles(final Collection roles)
		{
			this.roles = roles == null ? Collections.emptySet() : new HashSet<>(roles);
		}

		///////////////////////////////////////////////////////////////////////////
		// override methods //
		/////////////////////

		/**
		 * {@inheritDoc}
		 */
		@Override
		public final String name()
		{
			return this.name;
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public final Collection roles()
		{
			return this.roles;
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public final Collection permissions()
		{
			return this.permissions;
		}

		@Override
		public String toString()
		{
			return this.name;
		}
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy