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

net.krotscheck.kangaroo.authz.admin.v1.resource.RoleScopeService 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 io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.Authorization;
import io.swagger.annotations.AuthorizationScope;
import net.krotscheck.kangaroo.authz.admin.Scope;
import net.krotscheck.kangaroo.authz.admin.v1.auth.ScopesAllowed;
import net.krotscheck.kangaroo.authz.admin.v1.exception.InvalidEntityPropertyException;
import net.krotscheck.kangaroo.authz.common.database.entity.ApplicationScope;
import net.krotscheck.kangaroo.authz.common.database.entity.Role;
import net.krotscheck.kangaroo.authz.oauth2.exception.RFC6749.InvalidScopeException;
import net.krotscheck.kangaroo.common.hibernate.transaction.Transactional;
import org.glassfish.jersey.internal.inject.AbstractBinder;
import org.glassfish.jersey.process.internal.RequestScope;
import org.hibernate.Session;

import javax.ws.rs.ClientErrorException;
import javax.ws.rs.DELETE;
import javax.ws.rs.ForbiddenException;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.SecurityContext;
import java.math.BigInteger;


/**
 * A RESTful API that permits the management of application role resources.
 *
 * @author Michael Krotscheck
 */
@ScopesAllowed({Scope.ROLE, Scope.ROLE_ADMIN})
@Transactional
@Api(tags = "Role",
     authorizations = {
             @Authorization(value = "Kangaroo", scopes = {
                     @AuthorizationScope(
                             scope = Scope.ROLE,
                             description = "Modify scopes in one"
                                     + " application."),
                     @AuthorizationScope(
                             scope = Scope.ROLE_ADMIN,
                             description = "Modify scopes in all"
                                     + " applications.")
             })
     })
public final class RoleScopeService extends AbstractService {

    /**
     * The role from which the scopes are extracted.
     */
    private BigInteger roleId;

    /**
     * Set the role for this instance of the scope service.
     *
     * @param roleId The role id.
     */
    public void setRoleId(final BigInteger roleId) {
        this.roleId = roleId;
    }

    /**
     * Create a link between a role and a scope.
     *
     * @param scopeId The ID of the scope to link.
     * @return A redirect to the location where the role was created.
     */
    @POST
    @Path("/{id: [a-f0-9]{32}}")
    @ApiOperation(value = "Link a scope to a role")
    public Response createResource(
            @io.swagger.annotations.ApiParam(type = "string")
            @PathParam("id") final BigInteger scopeId) {
        Session s = getSession();
        SecurityContext security = getSecurityContext();

        // Make sure we're allowed to access the role.
        Role role = s.get(Role.class, roleId);
        assertCanAccess(role, getAdminScope());

        // Make sure we're allowed to access the scope. Since the scope check
        // for the second required scope is not handled for us, we have to do
        // it here.
        ApplicationScope scope = s.get(ApplicationScope.class, scopeId);
        if (!security.isUserInRole(Scope.SCOPE)
                && !security.isUserInRole(Scope.SCOPE_ADMIN)) {
            throw new InvalidScopeException();
        }
        assertCanAccess(scope, Scope.SCOPE_ADMIN);

        // If the parent application doesn't match, error.
        if (!role.getApplication().equals(scope.getApplication())) {
            throw new InvalidEntityPropertyException("application");
        }

        // If the role is already linked to this scope, error.
        if (role.getScopes().values().contains(scope)) {
            throw new ClientErrorException(Status.CONFLICT);
        }

        // If we're trying to modify the admin application, error.
        if (role.getApplication().equals(getAdminApplication())) {
            throw new ForbiddenException();
        }

        // Create the link.
        role.getScopes().put(scope.getName(), scope);
        s.update(role);

        return Response.created(getUriInfo().getAbsolutePath()).build();
    }


    /**
     * Delete a scope from a role.
     *
     * @param scopeId The Unique Identifier for the scope.
     * @return A response that indicates the success of this operation.
     */
    @DELETE
    @Path("/{id: [a-f0-9]{32}}")
    @ApiOperation(value = "Unlink a scope from a role")
    public Response deleteResource(
            @io.swagger.annotations.ApiParam(type = "string")
            @PathParam("id") final BigInteger scopeId) {
        Session s = getSession();
        SecurityContext security = getSecurityContext();

        // Make sure we're allowed to access the role.
        Role role = s.get(Role.class, roleId);
        assertCanAccess(role, getAdminScope());

        // Make sure we're allowed to access the scope. Since the scope check
        // for the second required scope is not handled for us, we have to do
        // it here.
        ApplicationScope scope = s.get(ApplicationScope.class, scopeId);
        if (!security.isUserInRole(Scope.SCOPE)
                && !security.isUserInRole(Scope.SCOPE_ADMIN)) {
            throw new InvalidScopeException();
        }
        assertCanAccess(scope, Scope.SCOPE_ADMIN);

        // If the scope's not assigned to the role, error.
        if (!role.getScopes().values().contains(scope)) {
            throw new NotFoundException();
        }

        // If we're in the admin app, we can't modify anything.
        if (getAdminApplication().equals(role.getApplication())) {
            throw new ForbiddenException();
        }

        // Execute the command.
        role.getScopes().remove(scope.getName());
        s.update(role);

        return Response.status(Status.RESET_CONTENT).build();
    }

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

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

    /**
     * HK2 Binder for our injector context.
     */
    public static final class Binder extends AbstractBinder {

        @Override
        protected void configure() {
            bind(RoleScopeService.class)
                    .to(RoleScopeService.class)
                    .to(RequestScope.class);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy