package org.springframework.roo.addon.security.addon.security;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Service;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.service.component.ComponentContext;
import org.springframework.roo.addon.pushin.PushInOperations;
import org.springframework.roo.addon.security.addon.security.providers.SecurityProvider;
import org.springframework.roo.addon.security.annotations.RooSecurityAuthorization;
import org.springframework.roo.addon.security.annotations.RooSecurityFilter;
import org.springframework.roo.classpath.TypeLocationService;
import org.springframework.roo.classpath.TypeManagementService;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetailsBuilder;
import org.springframework.roo.classpath.details.DefaultMethodMetadata;
import org.springframework.roo.classpath.details.annotations.AnnotatedJavaType;
import org.springframework.roo.classpath.details.annotations.AnnotationAttributeValue;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadata;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadataBuilder;
import org.springframework.roo.classpath.details.annotations.ArrayAttributeValue;
import org.springframework.roo.classpath.details.annotations.ClassAttributeValue;
import org.springframework.roo.classpath.details.annotations.NestedAnnotationAttributeValue;
import org.springframework.roo.classpath.details.annotations.StringAttributeValue;
import org.springframework.roo.model.JavaSymbolName;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.model.RooJavaType;
import org.springframework.roo.project.PathResolver;
import org.springframework.roo.project.ProjectOperations;
import org.springframework.roo.project.maven.Pom;
import org.springframework.roo.support.logging.HandlerUtils;
import org.springframework.roo.project.Dependency;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Logger;
/**
* Provides security installation services.
*
* @author Ben Alex
* @author Stefan Schmidt
* @author Alan Stewart
* @author Sergio Clares
* @author Juan Carlos GarcĂa
* @since 1.0
*/
@Component
@Service
public class SecurityOperationsImpl implements SecurityOperations {
protected final static Logger LOGGER = HandlerUtils.getLogger(SecurityOperationsImpl.class);
private static final Dependency SPRING_SECURITY_CORE = new Dependency(
"org.springframework.security", "spring-security-core", null);
// ------------ OSGi component attributes ----------------
private BundleContext context;
private ProjectOperations projectOperations;
private PathResolver pathResolver;
private TypeLocationService typeLocationService;
private TypeManagementService typeManagementService;
private PushInOperations pushInOperations;
private List<SecurityProvider> securityProviders = new ArrayList<SecurityProvider>();
protected void activate(final ComponentContext context) {
this.context = context.getBundleContext();
}
@Override
public void installSecurity(SecurityProvider type, Pom module) {
Validate.notNull(type, "ERROR: You must provide a valid SecurityProvider to install.");
// Delegates on the provided SecurityProvider to install Spring Security
// on current project
type.install(module);
}
public void generateFilterAnnotations(JavaType klass, String methodName, String roles,
String usernames, String when) {
// Get methods to annotate.
// With the last parameter to false, we avoid that push in action occurs.
List<Object> pushedElements =
getPushInOperations().pushIn(klass.getPackage(), klass, methodName, false);
List<AnnotationAttributeValue<?>> rooSecurityFiltersToAdd =
new ArrayList<AnnotationAttributeValue<?>>();
for (Object pushedElement : pushedElements) {
if (pushedElement instanceof DefaultMethodMetadata) {
DefaultMethodMetadata method = (DefaultMethodMetadata) pushedElement;
// Get parameters
List<AnnotationAttributeValue<?>> lstParamTypes =
new ArrayList<AnnotationAttributeValue<?>>();
List<AnnotatedJavaType> parameterTypes = method.getParameterTypes();
Iterator<AnnotatedJavaType> iterParamTypes = parameterTypes.iterator();
while (iterParamTypes.hasNext()) {
ClassAttributeValue parameterAttributeValue =
new ClassAttributeValue(new JavaSymbolName("value"), iterParamTypes.next()
.getJavaType());
lstParamTypes.add(parameterAttributeValue);
}
// Generate new annotations @RooSecurityFilter
NestedAnnotationAttributeValue newFilter =
new NestedAnnotationAttributeValue(new JavaSymbolName("value"),
getRooSecurityFilterAnnotation(method.getMethodName().getSymbolName(),
lstParamTypes, roles, usernames, when).build());
rooSecurityFiltersToAdd.add(newFilter);
}
}
// Get actual values of @RooSecurityFilters
ClassOrInterfaceTypeDetails serviceDetails = getTypeLocationService().getTypeDetails(klass);
ClassOrInterfaceTypeDetailsBuilder cidBuilder =
new ClassOrInterfaceTypeDetailsBuilder(serviceDetails);
// Check annotation @RooSecurityFilters to delete defined annotations
// that will be redefined
AnnotationMetadata annotationFilters =
serviceDetails.getAnnotation(RooJavaType.ROO_SECURITY_FILTERS);
AnnotationMetadataBuilder annotationFiltersMetadataBuilder;
if (annotationFilters != null) {
// Getting filters from annotation
AnnotationAttributeValue<?> attributeFilters = annotationFilters.getAttribute("filters");
List<?> values = (List<?>) attributeFilters.getValue();
if (values != null && !values.isEmpty()) {
Iterator<?> valuesIt = values.iterator();
while (valuesIt.hasNext()) {
NestedAnnotationAttributeValue filterAnnotation =
(NestedAnnotationAttributeValue) valuesIt.next();
if (checkRooSecurityFilterMaintainAnnotation(rooSecurityFiltersToAdd, filterAnnotation)) {
// Maintain annotation if 'method', 'parameters' or 'when' are different
rooSecurityFiltersToAdd.add(filterAnnotation);
}
}
}
annotationFiltersMetadataBuilder = new AnnotationMetadataBuilder(annotationFilters);
// remove annotation
cidBuilder.removeAnnotation(RooJavaType.ROO_SECURITY_FILTERS);
} else {
// Doesn't exist @RooSecurityFilters, create it
annotationFiltersMetadataBuilder =
new AnnotationMetadataBuilder(RooJavaType.ROO_SECURITY_FILTERS);
}
// Add filters attribute
ArrayAttributeValue<AnnotationAttributeValue<?>> newFilters =
new ArrayAttributeValue<AnnotationAttributeValue<?>>(new JavaSymbolName("filters"),
rooSecurityFiltersToAdd);
annotationFiltersMetadataBuilder.addAttribute(newFilters);
// Include new @RooSecurityFilters annotation
cidBuilder.addAnnotation(annotationFiltersMetadataBuilder);
// Write on disk
getTypeManagementService().createOrUpdateTypeOnDisk(cidBuilder.build());
// Add Spring Security dependency
getProjectOperations().addDependency(klass.getModule(), SPRING_SECURITY_CORE, false);
}
/**
* Check if {@link RooSecurityFilter} annotation should be kept
* or should be replaced because is defined in the annotations list to add.
*
* @param rooSecurityFiltersToAdd Annotations list to add
* @param filterAnnotation Annotation to check
* @return
*/
private boolean checkRooSecurityFilterMaintainAnnotation(
List<AnnotationAttributeValue<?>> rooSecurityFiltersToAdd,
NestedAnnotationAttributeValue filterAnnotation) {
boolean maintainAnnotation = true;
String annotationMethod =
(String) filterAnnotation.getValue().getAttribute("method").getValue();
List<?> annotationParameters =
(List<?>) filterAnnotation.getValue().getAttribute("parameters").getValue();
String annotationWhen = (String) filterAnnotation.getValue().getAttribute("when").getValue();
Iterator<AnnotationAttributeValue<?>> iterParamTypes = rooSecurityFiltersToAdd.iterator();
while (iterParamTypes.hasNext()) {
NestedAnnotationAttributeValue rooSecurityFilterToAdd =
(NestedAnnotationAttributeValue) iterParamTypes.next();
String annotationMethodToAdd =
(String) rooSecurityFilterToAdd.getValue().getAttribute("method").getValue();
List<?> annotationParametersToAdd =
(List<?>) rooSecurityFilterToAdd.getValue().getAttribute("parameters").getValue();
String annotationWhenToAdd =
(String) rooSecurityFilterToAdd.getValue().getAttribute("when").getValue();
boolean parametersAreEquals = true;
if (annotationParametersToAdd.size() != annotationParameters.size()) {
parametersAreEquals = false;
} else {
for (int i = 0; i < annotationParametersToAdd.size(); i++) {
ClassAttributeValue classAnnotationParametersToAdd =
(ClassAttributeValue) annotationParametersToAdd.get(i);
ClassAttributeValue classAnnotationParameters =
(ClassAttributeValue) annotationParameters.get(i);
if (!classAnnotationParametersToAdd.getValue().getSimpleTypeName()
.equals(classAnnotationParameters.getValue().getSimpleTypeName())) {
parametersAreEquals = false;
break;
}
}
}
if (annotationMethodToAdd.equals(annotationMethod)
&& annotationWhenToAdd.equals(annotationWhen) && parametersAreEquals) {
maintainAnnotation = false;
break;
}
}
return maintainAnnotation;
}
/**
* This method provides {@link RooSecurityFilter} annotation with all the necessary
* attributes
*
* @param method Method to add the annotation
* @param lstParamTypes Parameter types of the method to add the annotation
* @param roles Roles to apply by the filter
* @param usernames Usernames apply by the filter
* @param when Indicate the type of filter 'PRE' (@PreFilter) or 'POST' (@PostFilter)
* @return the annotation created
*/
private AnnotationMetadataBuilder getRooSecurityFilterAnnotation(final String method,
final List<AnnotationAttributeValue<?>> lstParamTypes, final String roles,
final String usernames, final String when) {
final List<AnnotationAttributeValue<?>> attributes =
new ArrayList<AnnotationAttributeValue<?>>();
attributes.add(new StringAttributeValue(new JavaSymbolName("method"), method));
ArrayAttributeValue<AnnotationAttributeValue<?>> newParameters =
new ArrayAttributeValue<AnnotationAttributeValue<?>>(new JavaSymbolName("parameters"),
lstParamTypes);
attributes.add(newParameters);
if (roles != null) {
attributes.add(new StringAttributeValue(new JavaSymbolName("roles"), roles));
}
if (usernames != null) {
attributes.add(new StringAttributeValue(new JavaSymbolName("usernames"), usernames));
}
attributes.add(new StringAttributeValue(new JavaSymbolName("when"), when));
return new AnnotationMetadataBuilder(RooJavaType.ROO_SECURITY_FILTER, attributes);
}
@Override
public void generateAuthorizeAnnotations(JavaType klass, String methodName, String roles,
String usernames) {
Validate.notNull(klass,
"ERROR: klass parameter is mandatory on 'generateAuthorizeAnnotations' method");
Validate.notNull(methodName,
"ERROR: method parameter is mandatory on 'generateAuthorizeAnnotations' method");
// Get methods to annotate.
// With the last parameter to false, we avoid that push in action occurs.
List<Object> pushedElements =
getPushInOperations().pushIn(klass.getPackage(), klass, methodName, false);
List<AnnotationAttributeValue<?>> rooSecurityAuthorizationsToAdd =
new ArrayList<AnnotationAttributeValue<?>>();
for (Object pushedElement : pushedElements) {
if (pushedElement instanceof DefaultMethodMetadata) {
DefaultMethodMetadata method = (DefaultMethodMetadata) pushedElement;
// Get parameters
List<AnnotationAttributeValue<?>> lstParamTypes =
new ArrayList<AnnotationAttributeValue<?>>();
List<AnnotatedJavaType> parameterTypes = method.getParameterTypes();
Iterator<AnnotatedJavaType> iterParamTypes = parameterTypes.iterator();
while (iterParamTypes.hasNext()) {
ClassAttributeValue parameterAttributeValue =
new ClassAttributeValue(new JavaSymbolName("value"), iterParamTypes.next()
.getJavaType());
lstParamTypes.add(parameterAttributeValue);
}
// Generate new annotations @RooSecurityAuthorization
NestedAnnotationAttributeValue newFilter =
new NestedAnnotationAttributeValue(new JavaSymbolName("value"),
getRooSecurityAuthorizationsAnnotation(method.getMethodName().getSymbolName(),
lstParamTypes, roles, usernames).build());
rooSecurityAuthorizationsToAdd.add(newFilter);
}
}
// Get actual values of @RooSecurityAuthorizations
ClassOrInterfaceTypeDetails serviceDetails = getTypeLocationService().getTypeDetails(klass);
ClassOrInterfaceTypeDetailsBuilder cidBuilder =
new ClassOrInterfaceTypeDetailsBuilder(serviceDetails);
// Check annotation @RooSecurityAuthorizations to delete defined annotations
// that will be redefined
AnnotationMetadata annotationAuthorizations =
serviceDetails.getAnnotation(RooJavaType.ROO_SECURITY_AUTHORIZATIONS);
AnnotationMetadataBuilder annotationAuthorizationsMetadataBuilder;
if (annotationAuthorizations != null) {
// Getting authorizations from annotation
AnnotationAttributeValue<?> attributeAuthorizations =
annotationAuthorizations.getAttribute("authorizations");
List<?> values = (List<?>) attributeAuthorizations.getValue();
if (values != null && !values.isEmpty()) {
Iterator<?> valuesIt = values.iterator();
while (valuesIt.hasNext()) {
NestedAnnotationAttributeValue authorizationAnnotation =
(NestedAnnotationAttributeValue) valuesIt.next();
if (checkRooSecurityAuthorizationMaintainAnnotation(rooSecurityAuthorizationsToAdd,
authorizationAnnotation)) {
// Maintain annotation if 'method' or 'parameters' are different
rooSecurityAuthorizationsToAdd.add(authorizationAnnotation);
}
}
}
annotationAuthorizationsMetadataBuilder =
new AnnotationMetadataBuilder(annotationAuthorizations);
// remove annotation
cidBuilder.removeAnnotation(RooJavaType.ROO_SECURITY_AUTHORIZATIONS);
} else {
// Doesn't exist @RooSecurityAuthorizations, create it
annotationAuthorizationsMetadataBuilder =
new AnnotationMetadataBuilder(RooJavaType.ROO_SECURITY_AUTHORIZATIONS);
}
// Add authorizations attribute
ArrayAttributeValue<AnnotationAttributeValue<?>> newAuthorizations =
new ArrayAttributeValue<AnnotationAttributeValue<?>>(new JavaSymbolName("authorizations"),
rooSecurityAuthorizationsToAdd);
annotationAuthorizationsMetadataBuilder.addAttribute(newAuthorizations);
// Include new @RooSecurityAuthorizations annotation
cidBuilder.addAnnotation(annotationAuthorizationsMetadataBuilder);
// Write on disk
getTypeManagementService().createOrUpdateTypeOnDisk(cidBuilder.build());
// Add Spring Security dependency
getProjectOperations().addDependency(klass.getModule(), SPRING_SECURITY_CORE, false);
}
/**
* This method provides {@link RooSecurityAuthorization} annotation with all the necessary
* attributes
*
* @param method Method to add the annotation
* @param lstParamTypes Parameter types of the method to add the annotation
* @param roles Roles to apply by the filter
* @param usernames Usernames apply by the filter
* @return the annotation created
*/
private AnnotationMetadataBuilder getRooSecurityAuthorizationsAnnotation(final String method,
final List<AnnotationAttributeValue<?>> lstParamTypes, final String roles,
final String usernames) {
final List<AnnotationAttributeValue<?>> attributes =
new ArrayList<AnnotationAttributeValue<?>>();
attributes.add(new StringAttributeValue(new JavaSymbolName("method"), method));
ArrayAttributeValue<AnnotationAttributeValue<?>> newParameters =
new ArrayAttributeValue<AnnotationAttributeValue<?>>(new JavaSymbolName("parameters"),
lstParamTypes);
attributes.add(newParameters);
if (roles != null) {
attributes.add(new StringAttributeValue(new JavaSymbolName("roles"), roles));
}
if (usernames != null) {
attributes.add(new StringAttributeValue(new JavaSymbolName("usernames"), usernames));
}
return new AnnotationMetadataBuilder(RooJavaType.ROO_SECURITY_AUTHORIZATION, attributes);
}
/**
* Check if {@link RooSecurityAuthorization} annotation should be kept
* or should be replaced because is defined in the annotations list to add.
*
* @param rooSecurityAuthorizationsToAdd Annotations list to add
* @param authorizationAnnotation Annotation to check
* @return
*/
private boolean checkRooSecurityAuthorizationMaintainAnnotation(
List<AnnotationAttributeValue<?>> rooSecurityAuthorizationsToAdd,
NestedAnnotationAttributeValue authorizationAnnotation) {
boolean maintainAnnotation = true;
String annotationMethod =
(String) authorizationAnnotation.getValue().getAttribute("method").getValue();
List<?> annotationParameters =
(List<?>) authorizationAnnotation.getValue().getAttribute("parameters").getValue();
Iterator<AnnotationAttributeValue<?>> iterParamTypes =
rooSecurityAuthorizationsToAdd.iterator();
while (iterParamTypes.hasNext()) {
NestedAnnotationAttributeValue rooSecurityAuthorizationToAdd =
(NestedAnnotationAttributeValue) iterParamTypes.next();
String annotationMethodToAdd =
(String) rooSecurityAuthorizationToAdd.getValue().getAttribute("method").getValue();
List<?> annotationParametersToAdd =
(List<?>) rooSecurityAuthorizationToAdd.getValue().getAttribute("parameters").getValue();
boolean parametersAreEquals = true;
if (annotationParametersToAdd.size() != annotationParameters.size()) {
parametersAreEquals = false;
} else {
for (int i = 0; i < annotationParametersToAdd.size(); i++) {
ClassAttributeValue classAnnotationParametersToAdd =
(ClassAttributeValue) annotationParametersToAdd.get(i);
ClassAttributeValue classAnnotationParameters =
(ClassAttributeValue) annotationParameters.get(i);
if (!classAnnotationParametersToAdd.getValue().getSimpleTypeName()
.equals(classAnnotationParameters.getValue().getSimpleTypeName())) {
parametersAreEquals = false;
break;
}
}
}
if (annotationMethodToAdd.equals(annotationMethod) && parametersAreEquals) {
maintainAnnotation = false;
break;
}
}
return maintainAnnotation;
}
@Override
public String getSpringSecurityAnnotationValue(String roles, String usernames) {
String value = "";
// Including roles
if (StringUtils.isNotEmpty(roles)) {
// First of all, obtain the comma separated list
// that contains all roles
String[] rolesList = roles.split(",");
// Now, check if there's more than one role
if (rolesList.length > 1) {
// create the hasAnyRole expression
value = "hasAnyRole(";
} else {
// create the hasRole expression
value = "hasRole(";
}
for (String role : rolesList) {
value = value.concat("'").concat(role).concat("'").concat(",");
}
value = value.substring(0, value.length() - 1).concat(")");
}
// Including usernames
if (StringUtils.isNotEmpty(usernames)) {
// First of all, obtain the comma separated list
// that contains all usernames
String[] usernamesList = usernames.split(",");
// Check if also exist some role added previously
if (StringUtils.isNotEmpty(value) && usernamesList.length > 0) {
value = value.concat(" or");
}
// Create (#username == principal.username) expression
for (String username : usernamesList) {
value = value.concat(" (#").concat(username).concat(" == principal.username) or");
}
// Removing last extra or
value = value.substring(0, value.length() - 3);
}
return value.trim();
}
@Override
public List<SecurityProvider> getAllSecurityProviders() {
if (securityProviders.isEmpty()) {
// Get all Services implement SecurityProvider interface
try {
ServiceReference<?>[] references =
this.context.getAllServiceReferences(SecurityProvider.class.getName(), null);
for (ServiceReference<?> ref : references) {
SecurityProvider securityProvider = (SecurityProvider) this.context.getService(ref);
securityProviders.add(securityProvider);
}
return securityProviders;
} catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load SecurityProvider on SecurityOperationsImpl.");
return null;
}
} else {
return securityProviders;
}
}
public ProjectOperations getProjectOperations() {
if (projectOperations == null) {
// Get all Services implement ProjectOperations interface
try {
ServiceReference<?>[] references =
this.context.getAllServiceReferences(ProjectOperations.class.getName(), null);
for (ServiceReference<?> ref : references) {
projectOperations = (ProjectOperations) this.context.getService(ref);
return projectOperations;
}
return null;
} catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load ProjectOperations on SecurityOperationsImpl.");
return null;
}
} else {
return projectOperations;
}
}
public TypeLocationService getTypeLocationService() {
if (typeLocationService == null) {
// Get all Services implement TypeLocationService interface
try {
ServiceReference<?>[] references =
this.context.getAllServiceReferences(TypeLocationService.class.getName(), null);
for (ServiceReference<?> ref : references) {
typeLocationService = (TypeLocationService) this.context.getService(ref);
return typeLocationService;
}
return null;
} catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load TypeLocationService on SecurityOperationsImpl.");
return null;
}
} else {
return typeLocationService;
}
}
public TypeManagementService getTypeManagementService() {
if (typeManagementService == null) {
// Get all Services implement TypeManagementService interface
try {
ServiceReference<?>[] references =
this.context.getAllServiceReferences(TypeManagementService.class.getName(), null);
for (ServiceReference<?> ref : references) {
typeManagementService = (TypeManagementService) this.context.getService(ref);
return typeManagementService;
}
return null;
} catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load TypeManagementService on SecurityOperationsImpl.");
return null;
}
} else {
return typeManagementService;
}
}
public PathResolver getPathResolver() {
if (pathResolver == null) {
// Get all Services implement PathResolver interface
try {
ServiceReference<?>[] references =
this.context.getAllServiceReferences(PathResolver.class.getName(), null);
for (ServiceReference<?> ref : references) {
pathResolver = (PathResolver) this.context.getService(ref);
return pathResolver;
}
return null;
} catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load PathResolver on SecurityOperationsImpl.");
return null;
}
} else {
return pathResolver;
}
}
public PushInOperations getPushInOperations() {
if (pushInOperations == null) {
// Get all Services implement PushInOperations interface
try {
ServiceReference<?>[] references =
this.context.getAllServiceReferences(PushInOperations.class.getName(), null);
for (ServiceReference<?> ref : references) {
pushInOperations = (PushInOperations) this.context.getService(ref);
return pushInOperations;
}
return null;
} catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load PushInOperations on SecurityOperationsImpl.");
return null;
}
} else {
return pushInOperations;
}
}
// FEATURE METHODS
@Override
public String getName() {
return SECURITY_FEATURE_NAME;
}
@Override
public boolean isInstalledInModule(String moduleName) {
List<SecurityProvider> providers = getAllSecurityProviders();
for (SecurityProvider provider : providers) {
if (provider.isInstalledInModule(moduleName)) {
return true;
}
}
return false;
}
}