/*
* Copyright 2015 JBoss Inc
*
* 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 io.apiman.gateway.engine.policies;
import io.apiman.gateway.engine.beans.ApiRequest;
import io.apiman.gateway.engine.beans.PolicyFailure;
import io.apiman.gateway.engine.beans.PolicyFailureType;
import io.apiman.gateway.engine.components.IPolicyFailureFactoryComponent;
import io.apiman.gateway.engine.policies.config.AuthorizationConfig;
import io.apiman.gateway.engine.policies.config.AuthorizationRule;
import io.apiman.gateway.engine.policies.config.MultipleMatchType;
import io.apiman.gateway.engine.policies.config.UnmatchedRequestType;
import io.apiman.gateway.engine.policies.i18n.Messages;
import io.apiman.gateway.engine.policy.IPolicyChain;
import io.apiman.gateway.engine.policy.IPolicyContext;
import java.util.HashSet;
import java.util.Set;
/**
* Adds authorization capabilities to apiman. This policy allows users to
* specify what roles the authenticated user must have in order to be allowed to
* call the API.
*
* This policy works in conjunction with a compatible Authentication policy,
* such as the Basic authentication policy. The assumption is that such a
* policy will extract the roles from the source of identity (either during
* authentication or as a followup step). These roles will be stored in the
* policy context for use by this Authorization policy. The roles are
* represented as a simple set of strings.
*
* @author eric.wittmann@redhat.com
*/
public class AuthorizationPolicy extends AbstractMappedPolicy<AuthorizationConfig> {
public static final String AUTHENTICATED_USER_ROLES = "io.apiman.policies.auth::authenticated-user-roles"; //$NON-NLS-1$
/**
* Constructor.
*/
public AuthorizationPolicy() {
}
/**
* @see io.apiman.gateway.engine.policies.AbstractMappedPolicy#getConfigurationClass()
*/
@Override
protected Class<AuthorizationConfig> getConfigurationClass() {
return AuthorizationConfig.class;
}
/**
* @see io.apiman.gateway.engine.policies.AbstractMappedPolicy#doApply(io.apiman.gateway.engine.beans.ApiRequest, io.apiman.gateway.engine.policy.IPolicyContext, java.lang.Object, io.apiman.gateway.engine.policy.IPolicyChain)
*/
@Override
protected void doApply(ApiRequest request, IPolicyContext context, AuthorizationConfig config,
IPolicyChain<ApiRequest> chain) {
Set<String> userRoles = context.getAttribute(AUTHENTICATED_USER_ROLES, (HashSet<String>) null);
String verb = request.getType();
String resource = request.getDestination();
// If no roles are set in the context - then fail with a configuration error
if (userRoles == null) {
String msg = Messages.i18n.format("AuthorizationPolicy.MissingRoles"); //$NON-NLS-1$
PolicyFailure failure = context.getComponent(IPolicyFailureFactoryComponent.class).createFailure(
PolicyFailureType.Other, PolicyFailureCodes.CONFIGURATION_ERROR, msg);
chain.doFailure(failure);
return;
}
if (isAuthorized(config, verb, resource, userRoles)) {
chain.doApply(request);
} else {
String msg = Messages.i18n.format("AuthorizationPolicy.Unauthorized"); //$NON-NLS-1$
PolicyFailure failure = context.getComponent(IPolicyFailureFactoryComponent.class).createFailure(
PolicyFailureType.Authorization, PolicyFailureCodes.USER_NOT_AUTHORIZED, msg);
chain.doFailure(failure);
}
}
/**
* Checks the verb and resource against the requirements configured for the
* policy. Returns true iff the user has all of the required roles (multiple
* roles may be required depending on configuration).
*
* Note that if the configuration does not include any
*
* @param config
* @param verb
* @param resource
* @param userRoles
*/
private boolean isAuthorized(AuthorizationConfig config, String verb, String resource, Set<String> userRoles) {
if (resource == null || resource.trim().length() == 0) {
resource = "/"; //$NON-NLS-1$
}
// If multiMatch is set to 'any', then start out with authorized = false, and we need to
// find at least one match to turn authorized to true. If it's set to "all" (the default)
// then start out authorized, and it requires *every* matching rule to pass or else it'll
// switch to false.
boolean authorized = true;
if (config.getMultiMatch() == MultipleMatchType.any) {
authorized = false;
}
boolean matchFound = false;
for (AuthorizationRule authorizationRule : config.getRules()) {
boolean verbMatches = "*".equals(authorizationRule.getVerb()) || verb.equalsIgnoreCase(authorizationRule.getVerb()); //$NON-NLS-1$
boolean ruleMatches = resource.matches(authorizationRule.getPathPattern());
if (verbMatches && ruleMatches) {
// the verb and resource matched the rule - so enforce the role here!
boolean userHasRole = userRoles.contains(authorizationRule.getRole());
matchFound = true;
// If the multiMatch setting is "at least one matching rule" then do a logical
// OR operation. If it's set to "all matching rules" then do a logical AND.
if (config.getMultiMatch() == MultipleMatchType.any) {
authorized = authorized || userHasRole;
} else {
authorized = authorized && userHasRole;
}
}
}
// If no authorization rules matched the request, what do we do?
if (!matchFound) {
if (config.getRequestUnmatched() == UnmatchedRequestType.pass) {
authorized = true;
} else {
authorized = false;
}
}
return authorized;
}
}