/** * 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 org.apache.aurora.scheduler.http.api.security; import java.lang.reflect.Method; import java.util.concurrent.atomic.AtomicLong; import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.util.concurrent.UncheckedExecutionException; import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.matcher.Matchers; import org.apache.aurora.common.stats.StatsProvider; import org.apache.aurora.common.testing.easymock.EasyMockTest; import org.apache.aurora.gen.JobConfiguration; import org.apache.aurora.gen.JobKey; import org.apache.aurora.gen.JobUpdateRequest; import org.apache.aurora.gen.Response; import org.apache.aurora.gen.ResponseCode; import org.apache.aurora.gen.TaskQuery; import org.apache.aurora.scheduler.base.JobKeys; import org.apache.aurora.scheduler.storage.entities.IJobKey; import org.apache.aurora.scheduler.thrift.Responses; import org.apache.aurora.scheduler.thrift.aop.AnnotatedAuroraAdmin; import org.apache.aurora.scheduler.thrift.aop.MockDecoratedThrift; import org.apache.shiro.subject.Subject; import org.apache.thrift.TException; import org.junit.Before; import org.junit.Test; import static org.apache.aurora.scheduler.http.api.security.ShiroAuthorizingParamInterceptor.SHIRO_AUTHORIZATION_FAILURES; import static org.apache.aurora.scheduler.http.api.security.ShiroAuthorizingParamInterceptor.SHIRO_BAD_REQUESTS; import static org.easymock.EasyMock.expect; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; public class ShiroAuthorizingParamInterceptorTest extends EasyMockTest { private ShiroAuthorizingParamInterceptor interceptor; private Subject subject; private AnnotatedAuroraAdmin thrift; private StatsProvider statsProvider; private AnnotatedAuroraAdmin decoratedThrift; private static final IJobKey JOB_KEY = JobKeys.from("role", "env", "name"); @Before public void setUp() { interceptor = new ShiroAuthorizingParamInterceptor(); subject = createMock(Subject.class); statsProvider = createMock(StatsProvider.class); thrift = createMock(AnnotatedAuroraAdmin.class); } private void replayAndInitialize() { expect(statsProvider.makeCounter(SHIRO_AUTHORIZATION_FAILURES)) .andReturn(new AtomicLong()); expect(statsProvider.makeCounter(SHIRO_BAD_REQUESTS)) .andReturn(new AtomicLong()); control.replay(); decoratedThrift = Guice .createInjector(new AbstractModule() { @Override protected void configure() { bind(Subject.class).toInstance(subject); MockDecoratedThrift.bindForwardedMock(binder(), thrift); bindInterceptor( Matchers.subclassesOf(AnnotatedAuroraAdmin.class), HttpSecurityModule.AURORA_SCHEDULER_MANAGER_SERVICE, interceptor); bind(StatsProvider.class).toInstance(statsProvider); requestInjection(interceptor); } }).getInstance(AnnotatedAuroraAdmin.class); } @Test public void testHandlesAllDecoratedParamTypes() { control.replay(); for (Method method : AnnotatedAuroraAdmin.class.getMethods()) { if (HttpSecurityModule.AURORA_SCHEDULER_MANAGER_SERVICE.matches(method)) { interceptor.getAuthorizingParamGetters().getUnchecked(method); } } } @Test public void testCreateJobWithScopedPermission() throws TException { JobConfiguration jobConfiguration = new JobConfiguration().setKey(JOB_KEY.newBuilder()); Response response = Responses.ok(); expect(subject .isPermitted(interceptor.makeTargetPermission("createJob", JOB_KEY))) .andReturn(true); expect(thrift.createJob(jobConfiguration)).andReturn(response); replayAndInitialize(); assertSame(response, decoratedThrift.createJob(jobConfiguration)); } @Test public void testKillTasksWithTargetedPermission() throws TException { expect(subject.isPermitted(interceptor.makeTargetPermission("killTasks", JOB_KEY))) .andReturn(false); expect(subject.getPrincipal()).andReturn("zmanji"); replayAndInitialize(); assertEquals( ResponseCode.AUTH_FAILED, decoratedThrift.killTasks(JOB_KEY.newBuilder(), null, null).getResponseCode()); } @Test public void testKillTasksInvalidJobKey() throws TException { replayAndInitialize(); assertEquals( ResponseCode.INVALID_REQUEST, decoratedThrift.killTasks( JOB_KEY.newBuilder().setName(null), null, null).getResponseCode()); } @Test public void testHandlesMultipleAnnotations() { control.replay(); Function<Object[], Optional<JobKey>> func = interceptor.getAuthorizingParamGetters().getUnchecked(Params.class.getMethods()[0]); func.apply(new Object[]{null, new JobKey(), null}); func.apply(new Object[]{null, null, new JobUpdateRequest()}); } @Test(expected = IllegalStateException.class) public void testThrowsOnMultipleNonNullArguments() { control.replay(); Function<Object[], Optional<JobKey>> func = interceptor.getAuthorizingParamGetters().getUnchecked(Params.class.getMethods()[0]); func.apply(new Object[]{new JobConfiguration(), new JobKey(), null}); } @Test(expected = UncheckedExecutionException.class) public void testThrowsNoAuthParams() { control.replay(); interceptor.getAuthorizingParamGetters().getUnchecked(NoParams.class.getMethods()[0]); } @Test(expected = UncheckedExecutionException.class) public void testThrowsNoResponseReturned() { control.replay(); interceptor.getAuthorizingParamGetters().getUnchecked(NoResponse.class.getMethods()[0]); } private interface NoResponse { void test(@AuthorizingParam TaskQuery query); } private interface NoParams { Response test(TaskQuery query); } private interface Params { Response test( @AuthorizingParam JobConfiguration jobConfig, @AuthorizingParam JobKey job, @AuthorizingParam JobUpdateRequest request); } }