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

net.krotscheck.kangaroo.authz.admin.v1.resource.AbstractService Maven / Gradle / Ivy

/*
 * Copyright (c) 2017 Michael Krotscheck
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy
 * of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package net.krotscheck.kangaroo.authz.admin.v1.resource;

import net.krotscheck.kangaroo.authz.admin.v1.exception.EntityRequiredException;
import net.krotscheck.kangaroo.authz.admin.v1.servlet.Config;
import net.krotscheck.kangaroo.authz.admin.v1.servlet.ServletConfigFactory;
import net.krotscheck.kangaroo.authz.common.database.entity.AbstractAuthzEntity;
import net.krotscheck.kangaroo.authz.common.database.entity.Application;
import net.krotscheck.kangaroo.authz.common.database.entity.OAuthToken;
import net.krotscheck.kangaroo.authz.common.database.entity.User;
import net.krotscheck.kangaroo.authz.oauth2.exception.RFC6749.InvalidScopeException;
import net.krotscheck.kangaroo.common.hibernate.entity.AbstractEntity;
import net.krotscheck.kangaroo.common.hibernate.id.IdUtil;
import net.krotscheck.kangaroo.common.hibernate.id.MalformedIdException;
import net.krotscheck.kangaroo.common.response.ListResponseBuilder;
import org.apache.commons.configuration.Configuration;
import org.glassfish.jersey.internal.inject.InjectionManager;
import org.hibernate.Session;
import org.hibernate.search.FullTextQuery;
import org.hibernate.search.FullTextSession;
import org.hibernate.search.SearchFactory;

import javax.inject.Inject;
import javax.inject.Named;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.core.UriInfo;
import java.math.BigInteger;

/**
 * Abstract implementation of our common services.
 *
 * @author Michael Krotscheck
 */
public abstract class AbstractService {

    /**
     * injection manager.
     */
    @Inject
    private InjectionManager injector;

    /**
     * Servlet Configuration.
     */
    @Inject
    @Named(ServletConfigFactory.GROUP_NAME)
    private Configuration config;

    /**
     * Hibernate session.
     */
    @Inject
    private Session session;

    /**
     * The hibernate search factory.
     */
    @Inject
    private SearchFactory searchFactory;

    /**
     * The hibernate fulltext session.
     */
    @Inject
    private FullTextSession fullTextSession;

    /**
     * The Security context.
     */
    @Inject
    private SecurityContext securityContext;

    /**
     * The URI info for this request.
     */
    @Inject
    private UriInfo uriInfo;

    /**
     * Retrieve the injector for this context.
     *
     * @return The locator.
     */
    public final InjectionManager getInjector() {
        return injector;
    }

    /**
     * Set (via injection, usually) the injector for this resource.
     *
     * @param injector The injector.
     */
    public final void setInjector(final InjectionManager injector) {
        this.injector = injector;
    }

    /**
     * Get the URI info for the current request.
     *
     * @return The URIInfo instance.
     */
    public final UriInfo getUriInfo() {
        return uriInfo;
    }

    /**
     * Set the URI info instance for the current request.
     *
     * @param uriInfo The new URIInfo instance.
     */
    public final void setUriInfo(final UriInfo uriInfo) {
        this.uriInfo = uriInfo;
    }

    /**
     * Get the session.
     *
     * @return The injected session.
     */
    public final Session getSession() {
        return session;
    }

    /**
     * Set the session.
     *
     * @param session The session.
     */
    public final void setSession(final Session session) {
        this.session = session;
    }

    /**
     * Retrieve the search factory.
     *
     * @return The injected search factory.
     */
    public final SearchFactory getSearchFactory() {
        return searchFactory;
    }

    /**
     * Set an instance of the search factory.
     *
     * @param searchFactory The search factory.
     */
    public final void setSearchFactory(final SearchFactory searchFactory) {
        this.searchFactory = searchFactory;
    }

    /**
     * Get the full text search session.
     *
     * @return The injected lucene session.
     */
    public final FullTextSession getFullTextSession() {
        return fullTextSession;
    }

    /**
     * Set the full text search session.
     *
     * @param fullTextSession The FTSession.
     */
    public final void setFullTextSession(
            final FullTextSession fullTextSession) {
        this.fullTextSession = fullTextSession;
    }

    /**
     * Get the configuration.
     *
     * @return A configuration!
     */
    public final Configuration getConfig() {
        return config;
    }

    /**
     * Set the request configuration.
     *
     * @param config A request configuration.
     */
    public final void setConfig(final Configuration config) {
        this.config = config;
    }

    /**
     * Get the security context.
     *
     * @return The request security context.
     */
    public final SecurityContext getSecurityContext() {
        return securityContext;
    }

    /**
     * Set a new security context.
     *
     * @param securityContext A new security context.
     */
    public final void setSecurityContext(
            final SecurityContext securityContext) {
        this.securityContext = securityContext;
    }

    /**
     * Retrieve the current user for this request.
     *
     * @return The current user, or null if not set.
     */
    protected final User getCurrentUser() {
        OAuthToken t = (OAuthToken) securityContext.getUserPrincipal();
        if (t.getIdentity() != null) {
            return t.getIdentity().getUser();
        }
        return null;
    }

    /**
     * Return the scope required to access ALL resources on this services.
     *
     * @return A string naming the scope.
     */
    protected abstract String getAdminScope();

    /**
     * Return the scope required to access resources on this service.
     *
     * @return A string naming the scope.
     */
    protected abstract String getAccessScope();

    /**
     * Retrieve the admin application database entry for the current running
     * test.
     *
     * @return The admin application, assuming it's been bootstrapped.
     */
    protected final Application getAdminApplication() {
        Session s = getSession();
        String configId = config.getString(Config.APPLICATION_ID);
        BigInteger appId = IdUtil.fromString(configId);
        return s.get(Application.class, appId);
    }

    /**
     * This method tests whether a particular entity may be accessed. This is
     * the case if one of two conditions is true. The first is simple- if the
     * user has the administration scope of ENTITYNAME_ADMIN, then they may
     * access this entity. If they only have the ENTITYNAME scope, then they
     * must also be the owner of this entity (the owner of the application to
     * which this entity belongs.
     *
     * @param entity        The entity to check.
     * @param requiredScope The scope required to access this
     *                      entity.
     */
    protected final void assertCanAccess(final AbstractAuthzEntity entity,
                                         final String requiredScope) {
        // No null entities permitted.
        if (entity == null) {
            throw new NotFoundException();
        }

        // Are we the entity owner?
        User u = getCurrentUser();
        if (u != null && u.equals(entity.getOwner())) {
            return;
        }

        // If we don't have the scope.
        if (getSecurityContext().isUserInRole(requiredScope)) {
            return;
        }

        // Not permitted, exit.
        throw new NotFoundException();
    }

    /**
     * Determine the appropriate owner on which we should be filtering.
     *
     * @param ownerId The passed-in owner id, could be null.
     * @return A , or null, indicating a valid filter action.
     */
    protected final User resolveOwnershipFilter(final BigInteger ownerId) {

        String adminScope = getAdminScope();

        if (getSecurityContext().isUserInRole(adminScope)) {
            if (ownerId == null) {
                return null;
            }
            User owner = getSession().get(User.class, ownerId);
            if (owner == null) {
                throw new BadRequestException();
            }
            return owner;
        }

        User currentUser = getCurrentUser();
        if (currentUser == null) {
            // This will catch ClientCredentials clients.
            throw new InvalidScopeException();
        }

        if (ownerId != null && !currentUser.getId().equals(ownerId)) {
            throw new InvalidScopeException();
        }

        return currentUser;
    }

    /**
     * Attempt to resolve the provided ID into the requested entity type.
     *
     * @param roleClass The entity type to resolve.
     * @param entityId  The ID to resolve.
     * @param        The return type.
     * @return The resolved entity, or an exception if it cannot be accessed.
     */
    protected final  T resolveFilterEntity(
            final Class roleClass,
            final BigInteger entityId) {

        String ownerScope = getAccessScope();
        String adminScope = getAdminScope();

        // Resolve the entity. This will error if an entity could not be
        // resolved.
        T entity = resolveEntityInput(roleClass, entityId);

        // If we're an admin, just return what we have so far.
        if (getSecurityContext().isUserInRole(adminScope)) {
            return entity;
        }

        // If we're not scoped, throw an exception
        if (!getSecurityContext().isUserInRole(ownerScope)) {
            throw new InvalidScopeException();
        }

        // If we don't have a current user, exit. This means that we have a
        // client-credentials client with no admin scope in the token.
        User currentUser = getCurrentUser();
        if (currentUser == null) {
            throw new InvalidScopeException();
        }

        // If we didn't ask for anything, return the default value.
        if (entityId == null) {
            return null;
        }

        // If we don't own the entity, bad request. This is identical to the
        // above, as we cannot expose to an improperly scoped token that
        // an entity might exist.
        if (!currentUser.equals(entity.getOwner())) {
            throw new BadRequestException();
        }

        return entity;
    }

    /**
     * Execute a search query.
     *
     * @param klass  The type to cast to.
     * @param query  The fulltext query to execute.
     * @param offset The query offset.
     * @param limit  The query limit.
     * @return A response with the return values of this type.
     */
    protected final Response executeQuery(
            final Class klass,
            final FullTextQuery query,
            final int offset,
            final int limit) {
        query.setFirstResult(offset);
        query.setMaxResults(limit);

        return ListResponseBuilder.builder()
                .offset(offset)
                .limit(limit)
                .addResult(query.list())
                .total(query.getResultSize())
                .build();
    }

    /**
     * Provided a type and an entity, attempts to resolve a fresh entity from
     * the database session.
     *
     * @param requestedType The type to resolve.
     * @param entity        The entity.
     * @param            The type to return (same as type to resolve).
     * @return Null if provided entity is null, otherwise entity.
     */
    protected final  K resolveEntityInput(
            final Class requestedType,
            final K entity) {

        // Null value check.
        if (entity == null) {
            return null;
        }

        return resolveEntityInput(requestedType, entity.getId());
    }


    /**
     * Provided a type and an id, attempts to resolve a fresh entity from
     * the database session.
     *
     * @param requestedType The type to resolve.
     * @param entityId      The entity id.
     * @param            The type to return (same as type to resolve).
     * @return Null if provided entity is null, otherwise entity.
     */
    protected final  K resolveEntityInput(
            final Class requestedType,
            final BigInteger entityId) {
        // Sanity check
        if (entityId == null) {
            return null;
        }

        // Attempt to resolve...
        K entity = getSession().get(requestedType, entityId);
        if (entity == null) {
            throw new MalformedIdException();
        }
        return entity;
    }

    /**
     * Provided a type and an entity, requires the existence of this entity.
     *
     * @param requestedType The type to resolve.
     * @param entity        The entity.
     * @param            The type to return (same as type to resolve).
     * @return Null if provided entity is null, otherwise entity.
     */
    protected final  K requireEntityInput(
            final Class requestedType,
            final K entity) {
        K resolved = resolveEntityInput(requestedType, entity);
        if (resolved == null) {
            throw new EntityRequiredException();
        }
        return resolved;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy