/* * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * * Alfresco is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Alfresco 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see . */ package org.alfresco.util.test.junitrules; import java.lang.annotation.Annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.InvocationTargetException; import java.util.Collection; import junit.framework.Test; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.util.ParameterCheck; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; /** * This JUnit rule can be used to make test methods run as a particular user. * A username can be provided on construction to the rule and then all @Test methods will be * run as that user. *

* Furthermore, if an individual test method is annotated like this @RunAsUser(userName="John") than that * method (and only that method) will be run as "John". *

* Example usage: *

 * public class YourTestClass
 * {
 *     @ClassRule public static final ApplicationContextInit APP_CONTEXT_RULE = new ApplicationContextInit();
 *     @Rule public RunAsFullyAuthenticatedRule runAsGuidPerson = new RunAsFullyAuthenticatedRule("NeilM");
 *     
 *     @Test public void doSomething() throws Exception
 *     {
 *         // This will run as NeilM
 *     }
 *     
 *     @Test @RunAsUser(userName="DaveC") public void doSomething() throws Exception
 *     {
 *         // This will run as DaveC
 *     }
 * }
 * 
* * @author Neil Mc Erlean * @since Odin */ public class RunAsFullyAuthenticatedRule implements TestRule { /** * This annotation can be used to mark an individual {@link Test} method for running as a named user. * * @author Neil Mc Erlean * @since Odin */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RunAsUser { String userName() default ""; } private static final Log log = LogFactory.getLog(RunAsFullyAuthenticatedRule.class); /** * A fixed username to run as. */ private final String fixedUserName; /** * A rule which will provide a username to run as */ private final AlfrescoPerson personRule; /** * This constructs a rule where there is no specified user to run as. * For this to be useful (or legal) a user must be specified on every test method using {@link RunAsUser}. */ public RunAsFullyAuthenticatedRule() { this.fixedUserName = null; this.personRule = null; } /** * @param userName the username which all test methods should run as. */ public RunAsFullyAuthenticatedRule(String userName) { ParameterCheck.mandatory("userName", userName); this.fixedUserName = userName; this.personRule = null; } /** * @param personRule the rule which will provide the username which all test methods should run as. */ public RunAsFullyAuthenticatedRule(AlfrescoPerson personRule) { ParameterCheck.mandatory("personRule", personRule); this.fixedUserName = null; this.personRule = personRule; } /** * Get the username which test methods will run as. */ public String getUsername() { return this.fixedUserName; } @Override public Statement apply(final Statement base, final Description description) { return new Statement() { @Override public void evaluate() throws Throwable { // Store the current authentication AuthenticationUtil.pushAuthentication(); // First, try for a username provided on the @Test method itself. String runAsUser = getMethodAnnotatedUserName(description); if (runAsUser != null) { // There is a @Test method username. log.debug("Running as method annotation-provided user: " + runAsUser); log.debug(" See " + description.getClassName() + "." + description.getMethodName()); } else { // There is no @Test method username, so fall back to rule-provided person. if (fixedUserName != null) { runAsUser = fixedUserName; log.debug("Running as username defined in this rule: " + runAsUser); } else if (personRule != null) { runAsUser = personRule.getUsername(); log.debug("Running as username provided by another rule: " + runAsUser); } else { throw new Exception("Illegal rule: must provide username or " + AlfrescoPerson.class.getSimpleName() + " at rule construction or else a " + RunAsUser.class.getSimpleName() + " annotation."); } } AuthenticationUtil.setFullyAuthenticatedUser(runAsUser); try { // Execute the test method or whatever other rules are configured further down the stack. base.evaluate(); } finally { // After - ensure that pass or fail, the authentication is restored. AuthenticationUtil.popAuthentication(); } } }; } /** * * @param description the description object from JUnit * @return the username specified in the {@link RunAsUser} annotation, if there was one, else null. */ private String getMethodAnnotatedUserName(Description description) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { String result = null; Collection annotations = description.getAnnotations(); for (Annotation anno : annotations) { if (anno.annotationType().equals(RunAsUser.class)) { result = (String) anno.annotationType().getMethod("userName").invoke(anno); } } return result; } }