Repository: aries-jax-rs-whiteboard Updated Branches: refs/heads/master 3ffc07222 -> 16da11274
Add an integration for Apache Shiro to the JAX-RS Whiteboard Project: http://git-wip-us.apache.org/repos/asf/aries-jax-rs-whiteboard/repo Commit: http://git-wip-us.apache.org/repos/asf/aries-jax-rs-whiteboard/commit/16da1127 Tree: http://git-wip-us.apache.org/repos/asf/aries-jax-rs-whiteboard/tree/16da1127 Diff: http://git-wip-us.apache.org/repos/asf/aries-jax-rs-whiteboard/diff/16da1127 Branch: refs/heads/master Commit: 16da11274073e47dbdc633d31c14631219729d2c Parents: 3ffc072 Author: Tim Ward <[email protected]> Authored: Tue Aug 7 16:07:48 2018 +0100 Committer: Tim Ward <[email protected]> Committed: Wed Aug 8 10:32:32 2018 +0100 ---------------------------------------------------------------------- README.md | 8 +- integrations/README.md | 40 +++ integrations/pom.xml | 44 +++ integrations/shiro/README.md | 23 ++ integrations/shiro/pom.xml | 51 ++++ integrations/shiro/shiro-authc/bnd.bnd | 27 ++ integrations/shiro/shiro-authc/pom.xml | 68 +++++ .../impl/SecurityManagerAssociatingFilter.java | 161 ++++++++++ .../impl/ShiroAuthenticationActivator.java | 137 +++++++++ .../authc/impl/ShiroAuthenticationFeature.java | 128 ++++++++ .../ShiroAuthenticationFeatureProvider.java | 43 +++ integrations/shiro/shiro-authz/bnd.bnd | 27 ++ integrations/shiro/shiro-authz/pom.xml | 68 +++++ .../authz/impl/ShiroAuthorizationActivator.java | 94 ++++++ .../authz/impl/ShiroAuthorizationFeature.java | 101 ++++++ integrations/shiro/shiro-itest/bnd.bnd | 22 ++ integrations/shiro/shiro-itest/itest.bndrun | 72 +++++ integrations/shiro/shiro-itest/pom.xml | 208 +++++++++++++ .../src/main/java/test/ShiroTest.java | 192 ++++++++++++ .../src/main/java/test/types/TestHelper.java | 306 +++++++++++++++++++ .../java/test/types/TestShiroAnnotations.java | 58 ++++ .../shiro-itest/src/test/resources/logback.xml | 35 +++ 22 files changed, 1911 insertions(+), 2 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/aries-jax-rs-whiteboard/blob/16da1127/README.md ---------------------------------------------------------------------- diff --git a/README.md b/README.md index 0c61a81..3c5345a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ ## JAX-RS Whiteboard -This is an implementation of a JAX-RS Services whiteboard [OSGi RFC-217](https://github.com/osgi/design/tree/master/rfcs/rfc0217). +Aries JAX-RS Whiteboard is the reference implementation of the [OSGi JAX-RS Services Whiteboard 1.0](https://osgi.org/specification/osgi.cmpn/7.0.0/service.jaxrs.html). + +## Integrations + +The `integrations` folder contains OSGi enabled integrations for a variety of useful libraries that you might want to use with JAX-RS. In many cases these are just adding OSGi lifecycle and configuration to existing JAX-RS enabled libraries. ## Building @@ -31,4 +35,4 @@ java -jar jax-rs.example-run/example.jar 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. \ No newline at end of file + limitations under the License. http://git-wip-us.apache.org/repos/asf/aries-jax-rs-whiteboard/blob/16da1127/integrations/README.md ---------------------------------------------------------------------- diff --git a/integrations/README.md b/integrations/README.md new file mode 100644 index 0000000..16ede67 --- /dev/null +++ b/integrations/README.md @@ -0,0 +1,40 @@ +## Integrations + +This folder contains OSGi enabled integrations for a variety of useful libraries that you might want to use with JAX-RS. In many cases these are just adding OSGi lifecycle and configuration to existing JAX-RS enabled libraries. + +### Building the integration projects + +There is a single reactor pom which builds all of the integration projects, however as each project will evolve at different rates they are designed to be built and released separately from the whiteboard and other integration projects. + +## Apache Shiro + +[Apache Shiro](https://shiro.apache.org) is an authentication and authorization framework. This integration provides: + +Authentication: + +* Support for authenticating users using Apache Shiro +* Cookie based user memory +* Session based logout + +Authorization: + +* Support for injection of Shiro Security Contexts into your JAX-RS resources +* Support for Shiro authorization annotations on your JAX-RS resources + + +## License + + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You 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. http://git-wip-us.apache.org/repos/asf/aries-jax-rs-whiteboard/blob/16da1127/integrations/pom.xml ---------------------------------------------------------------------- diff --git a/integrations/pom.xml b/integrations/pom.xml new file mode 100644 index 0000000..d3004f3 --- /dev/null +++ b/integrations/pom.xml @@ -0,0 +1,44 @@ +<?xml version='1.0' encoding='UTF-8' ?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + <modelVersion>4.0.0</modelVersion> + + <groupId>org.apache.aries.jax.rs</groupId> + <artifactId>integration</artifactId> + <name>Apache Aries JAX-RS Integration Projects Reactor</name> + <version>1.0.0-SNAPSHOT</version> + <packaging>pom</packaging> + + <properties> + <maven.deploy.skip>true</maven.deploy.skip> + <maven.install.skip>true</maven.install.skip> + </properties> + + <scm> + <connection>scm:git:https://git-wip-us.apache.org/repos/asf/aries-jax-rs-whiteboard.git</connection> + <developerConnection>scm:git:https://git-wip-us.apache.org/repos/asf/aries-jax-rs-whiteboard.git</developerConnection> + <url>https://git-wip-us.apache.org/repos/asf/aries-jax-rs-whiteboard.git</url> + <tag>HEAD</tag> + </scm> + + <modules> + <module>shiro</module> + </modules> + +</project> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/aries-jax-rs-whiteboard/blob/16da1127/integrations/shiro/README.md ---------------------------------------------------------------------- diff --git a/integrations/shiro/README.md b/integrations/shiro/README.md new file mode 100644 index 0000000..9957d4f --- /dev/null +++ b/integrations/shiro/README.md @@ -0,0 +1,23 @@ +## Apache Shiro + +[Apache Shiro](https://shiro.apache.org) is an authentication and authorization framework. This integration provides two separate bundles which do not necessarily both need to be deployed (although they integrate well together + +### Shiro Authc + +This bundle provides a Shiro-based authentication REST resource for your users. + +Key features + +* Support for authenticating users using Apache Shiro +* Cookie based user memory +* Http Session based +* Logout support + + +### Shiro Authz + +This bundle provides Shiro-based authorization for your REST resources. This can be used with the Shiro Authc component, or Shiro authentication can be achieved using other means, such as the Shiro Servlet Filter. + +* Support for injection of Shiro Security Contexts into your JAX-RS resources +* Support for Shiro authorization annotations on your JAX-RS resources + http://git-wip-us.apache.org/repos/asf/aries-jax-rs-whiteboard/blob/16da1127/integrations/shiro/pom.xml ---------------------------------------------------------------------- diff --git a/integrations/shiro/pom.xml b/integrations/shiro/pom.xml new file mode 100644 index 0000000..7b460d4 --- /dev/null +++ b/integrations/shiro/pom.xml @@ -0,0 +1,51 @@ +<?xml version='1.0' encoding='UTF-8' ?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.aries.jax.rs</groupId> + <artifactId>org.apache.aries.jax.rs</artifactId> + <version>1.0.0</version> + <relativePath /> + </parent> + + <artifactId>shiro-integration</artifactId> + <name>Apache Shiro JAX-RS Integration Project</name> + <version>1.0.0-SNAPSHOT</version> + <packaging>pom</packaging> + <properties> + <aries.jaxrs.version>1.0.0</aries.jaxrs.version> + <shiro.version>1.4.0</shiro.version> + </properties> + + <scm> + <connection>scm:git:https://git-wip-us.apache.org/repos/asf/aries-jax-rs-whiteboard.git</connection> + <developerConnection>scm:git:https://git-wip-us.apache.org/repos/asf/aries-jax-rs-whiteboard.git</developerConnection> + <url>https://git-wip-us.apache.org/repos/asf/aries-jax-rs-whiteboard.git</url> + <tag>HEAD</tag> + </scm> + + <modules> + <module>shiro-authc</module> + <module>shiro-authz</module> + <module>shiro-itest</module> + </modules> + +</project> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/aries-jax-rs-whiteboard/blob/16da1127/integrations/shiro/shiro-authc/bnd.bnd ---------------------------------------------------------------------- diff --git a/integrations/shiro/shiro-authc/bnd.bnd b/integrations/shiro/shiro-authc/bnd.bnd new file mode 100644 index 0000000..b84fda6 --- /dev/null +++ b/integrations/shiro/shiro-authc/bnd.bnd @@ -0,0 +1,27 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# Ideally we would not include this, but for some reason the Shiro JAX-RS +# Support is not an OSGi bundle + +Export-Package: org.apache.shiro.web.jaxrs;version=${shiro.version} + +Conditional-Package: org.apache.aries.component.dsl.* + +-contract: * + +Bundle-Activator: org.apache.aries.jax.rs.shiro.authc.impl.ShiroAuthenticationActivator \ No newline at end of file http://git-wip-us.apache.org/repos/asf/aries-jax-rs-whiteboard/blob/16da1127/integrations/shiro/shiro-authc/pom.xml ---------------------------------------------------------------------- diff --git a/integrations/shiro/shiro-authc/pom.xml b/integrations/shiro/shiro-authc/pom.xml new file mode 100644 index 0000000..5ec42c4 --- /dev/null +++ b/integrations/shiro/shiro-authc/pom.xml @@ -0,0 +1,68 @@ +<?xml version='1.0' encoding='UTF-8' ?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.aries.jax.rs</groupId> + <artifactId>shiro-integration</artifactId> + <version>1.0.0-SNAPSHOT</version> + </parent> + + <artifactId>org.apache.aries.jax.rs.shiro.authc</artifactId> + <description>Apache Aries JAX-RS Shiro Authentication Provider</description> + <name>Apache Aries JAX-RS Shiro Authentication</name> + + <dependencies> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.core</artifactId> + <version>6.0.0</version> + </dependency> + <dependency> + <groupId>org.apache.aries.component-dsl</groupId> + <artifactId>org.apache.aries.component-dsl.component-dsl</artifactId> + <version>1.2.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.aries.spec</groupId> + <artifactId>org.apache.aries.javax.jax.rs-api</artifactId> + <version>1.0.0</version> + </dependency> + <dependency> + <groupId>org.apache.shiro</groupId> + <artifactId>shiro-jaxrs</artifactId> + <version>${shiro.version}</version> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.service.jaxrs</artifactId> + <version>1.0.0</version> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>biz.aQute.bnd</groupId> + <artifactId>bnd-maven-plugin</artifactId> + </plugin> + </plugins> + </build> + +</project> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/aries-jax-rs-whiteboard/blob/16da1127/integrations/shiro/shiro-authc/src/main/java/org/apache/aries/jax/rs/shiro/authc/impl/SecurityManagerAssociatingFilter.java ---------------------------------------------------------------------- diff --git a/integrations/shiro/shiro-authc/src/main/java/org/apache/aries/jax/rs/shiro/authc/impl/SecurityManagerAssociatingFilter.java b/integrations/shiro/shiro-authc/src/main/java/org/apache/aries/jax/rs/shiro/authc/impl/SecurityManagerAssociatingFilter.java new file mode 100644 index 0000000..6fa1d82 --- /dev/null +++ b/integrations/shiro/shiro-authc/src/main/java/org/apache/aries/jax/rs/shiro/authc/impl/SecurityManagerAssociatingFilter.java @@ -0,0 +1,161 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.aries.jax.rs.shiro.authc.impl; + +import static javax.ws.rs.core.HttpHeaders.SET_COOKIE; +import static org.apache.aries.jax.rs.shiro.authc.impl.ShiroAuthenticationFeature.SESSION_COOKIE_NAME; + +import java.io.IOException; + +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.container.ContainerResponseContext; +import javax.ws.rs.container.ContainerResponseFilter; +import javax.ws.rs.container.PreMatching; +import javax.ws.rs.core.Cookie; +import javax.ws.rs.core.NewCookie; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.ResponseBuilder; +import javax.ws.rs.core.Response.Status; +import javax.ws.rs.core.UriInfo; + +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.authc.UsernamePasswordToken; +import org.apache.shiro.mgt.SecurityManager; +import org.apache.shiro.subject.Subject; +import org.apache.shiro.util.ThreadContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This Filter is used to: + * + * <ul> + * <li>Associate a SecurityManager with the request</li> + * <li>Set up the current user if a cookie is detected</li> + * <li>Remove the user cookie after the request if the user is logged out</li> + * </ul> + */ +@PreMatching +public class SecurityManagerAssociatingFilter implements ContainerRequestFilter, ContainerResponseFilter { + + private static final Logger _LOG = LoggerFactory.getLogger( + SecurityManagerAssociatingFilter.class); + + private final SecurityManager manager; + + public SecurityManagerAssociatingFilter(SecurityManager manager) { + this.manager = manager; + } + + /** + * Set up the incoming request context + */ + @Override + public void filter(ContainerRequestContext requestContext) throws IOException { + + _LOG.debug("Establishing Shiro Security Context"); + + // Bind the security manager + ThreadContext.bind(manager); + + Cookie cookie = requestContext.getCookies().get(SESSION_COOKIE_NAME); + + // If we have a session cookie then use it to prime the session value + if(cookie != null) { + _LOG.debug("Found a Shiro Security Context cookie: {}. Establishing user context", cookie); + + _LOG.debug("Establishing user context:"); + Subject subject = new Subject.Builder(manager).sessionId(cookie.getValue()).buildSubject(); + ThreadContext.bind(subject); + if(_LOG.isDebugEnabled()) { + _LOG.debug("Established user context for: {}", subject.getPrincipal()); + } + } + + UriInfo info = requestContext.getUriInfo(); + + if("security/authenticate".equals(info.getPath())) { + requestContext.abortWith(authenticate(info, requestContext.getHeaderString("user"), requestContext.getHeaderString("password"))); + } else if("security/logout".equals(info.getPath())) { + logout(); + } + } + + /** + * Clean up after the request + */ + @Override + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) + throws IOException { + _LOG.debug("Cleaning up the Shiro Security Context"); + Subject subject = ThreadContext.getSubject(); + ThreadContext.unbindSecurityManager(); + ThreadContext.unbindSubject(); + + if(subject != null && !subject.isAuthenticated()) { + // Not authenticated. Check for incoming session cookie + Cookie cookie = requestContext.getCookies().get(SESSION_COOKIE_NAME); + + // If we have a session cookie then it should be deleted + if(cookie != null) { + _LOG.debug("The subject associated with this request is not authenticated, removing the session cookie"); + responseContext.getHeaders().add(SET_COOKIE, getDeletionCookie(requestContext)); + } + } + + } + + private NewCookie getDeletionCookie(ContainerRequestContext requestContext) { + return new NewCookie(SESSION_COOKIE_NAME, "deleteMe", + requestContext.getUriInfo().getBaseUri().getPath(), null, -1, null, -1, null, false, true); + } + + private Response authenticate(UriInfo info, String user, String password) { + + _LOG.debug("Received a login request for user {}", user); + + Subject currentUser = SecurityUtils.getSubject(); + + ResponseBuilder rb; + + if (!currentUser.isAuthenticated()) { + _LOG.debug("Authenticating user {}", user); + UsernamePasswordToken token = new UsernamePasswordToken(user, password); + token.setRememberMe(true); + currentUser.login(token); + + rb = Response.ok() + .cookie(new NewCookie(SESSION_COOKIE_NAME, currentUser.getSession().getId().toString(), + info.getBaseUri().getPath(), null, -1, null, -1, null, false, true)); + } else { + _LOG.debug("The login request for user {} was already authenticated as user {}", user, currentUser.getPrincipal()); + rb = Response.status(Status.CONFLICT); + } + return rb.build(); + } + + private void logout() { + _LOG.debug("Received a logout request"); + Subject currentUser = SecurityUtils.getSubject(); + + if (currentUser.isAuthenticated()) { + _LOG.debug("Logging out user {}", currentUser.getPrincipal()); + currentUser.logout(); + } + } +} http://git-wip-us.apache.org/repos/asf/aries-jax-rs-whiteboard/blob/16da1127/integrations/shiro/shiro-authc/src/main/java/org/apache/aries/jax/rs/shiro/authc/impl/ShiroAuthenticationActivator.java ---------------------------------------------------------------------- diff --git a/integrations/shiro/shiro-authc/src/main/java/org/apache/aries/jax/rs/shiro/authc/impl/ShiroAuthenticationActivator.java b/integrations/shiro/shiro-authc/src/main/java/org/apache/aries/jax/rs/shiro/authc/impl/ShiroAuthenticationActivator.java new file mode 100644 index 0000000..8fde563 --- /dev/null +++ b/integrations/shiro/shiro-authc/src/main/java/org/apache/aries/jax/rs/shiro/authc/impl/ShiroAuthenticationActivator.java @@ -0,0 +1,137 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.aries.jax.rs.shiro.authc.impl; + +import static java.lang.Boolean.TRUE; +import static org.apache.aries.component.dsl.OSGi.coalesce; +import static org.apache.aries.component.dsl.OSGi.configuration; +import static org.apache.aries.component.dsl.OSGi.just; +import static org.apache.aries.component.dsl.OSGi.register; +import static org.apache.aries.component.dsl.OSGi.serviceReferences; +import static org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants.JAX_RS_EXTENSION; + +import java.util.ArrayList; +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentSkipListMap; + +import javax.ws.rs.core.Feature; + +import org.apache.aries.component.dsl.CachingServiceReference; +import org.apache.aries.component.dsl.OSGi; +import org.apache.aries.component.dsl.OSGiResult; +import org.apache.shiro.realm.Realm; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ShiroAuthenticationActivator implements BundleActivator { + + private static final Logger _LOG = LoggerFactory.getLogger( + ShiroAuthenticationActivator.class); + + private OSGiResult registration; + + private BundleContext context; + + @Override + public void start(BundleContext context) throws Exception { + + this.context = context; + _LOG.debug("Starting the Shiro JAX-RS Authentication Feature"); + + registration = coalesce( + configuration("org.apache.aries.jax.rs.shiro.authentication"), + just(() -> { + + _LOG.debug("Using the default configuration for the Shiro JAX-RS Authentication Feature"); + + Dictionary<String, Object> properties = + new Hashtable<>(); + + properties.put( + Constants.SERVICE_PID, + "org.apache.aries.jax.rs.shiro.authentication"); + + return properties; + }) + ).map(this::filter) + .flatMap(this::setupRealms) + .run(context); + } + + Map<String, Object> filter(Dictionary<String, ?> props) { + Map<String, Object> serviceProps = new Hashtable<>(); + + Enumeration<String> keys = props.keys(); + while(keys.hasMoreElements()) { + String key = keys.nextElement(); + if(!key.startsWith(".")) { + serviceProps.put(key, props.get(key)); + } + } + + serviceProps.put(JAX_RS_EXTENSION, TRUE); + + _LOG.debug("Shiro JAX-RS Authentication Feature service properties are: {}", serviceProps); + + return serviceProps; + } + + OSGi<?> setupRealms(Map<String, Object> properties) { + + Object filter = properties.get("realms.target"); + + ConcurrentSkipListMap<CachingServiceReference<Realm>, Realm> discovered = new ConcurrentSkipListMap<>(); + + OSGi<List<Realm>> realms; + if(filter == null) { + _LOG.debug("The Shiro JAX-RS Authentication Feature is accepting all realms"); + realms = serviceReferences(Realm.class) + .effects(csr -> discovered.put(csr, context.getService(csr.getServiceReference())), + csr -> { + discovered.remove(csr); + context.ungetService(csr.getServiceReference()); + }) + .map(x -> new ArrayList<>(discovered.values())); + } else { + _LOG.debug("The Shiro JAX-RS Authentication Feature is filtering realms using the filter {}", filter); + realms = serviceReferences(Realm.class, String.valueOf(filter)) + .effects(csr -> discovered.put(csr, context.getService(csr.getServiceReference())), + csr -> { + discovered.remove(csr); + context.ungetService(csr.getServiceReference()); + }) + .map(x -> new ArrayList<>(discovered.values())); + } + + return realms.map(ShiroAuthenticationFeatureProvider::new) + .flatMap(f -> register(Feature.class, f, properties) + .effects(x -> {}, x -> f.close())); + } + + @Override + public void stop(BundleContext context) throws Exception { + _LOG.debug("Stopping the Shiro JAX-RS Authentication Feature"); + registration.close(); + } +} http://git-wip-us.apache.org/repos/asf/aries-jax-rs-whiteboard/blob/16da1127/integrations/shiro/shiro-authc/src/main/java/org/apache/aries/jax/rs/shiro/authc/impl/ShiroAuthenticationFeature.java ---------------------------------------------------------------------- diff --git a/integrations/shiro/shiro-authc/src/main/java/org/apache/aries/jax/rs/shiro/authc/impl/ShiroAuthenticationFeature.java b/integrations/shiro/shiro-authc/src/main/java/org/apache/aries/jax/rs/shiro/authc/impl/ShiroAuthenticationFeature.java new file mode 100644 index 0000000..f294a0b --- /dev/null +++ b/integrations/shiro/shiro-authc/src/main/java/org/apache/aries/jax/rs/shiro/authc/impl/ShiroAuthenticationFeature.java @@ -0,0 +1,128 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.aries.jax.rs.shiro.authc.impl; + +import static javax.ws.rs.Priorities.AUTHENTICATION; +import static javax.ws.rs.Priorities.AUTHORIZATION; +import static javax.ws.rs.Priorities.USER; +import static org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants.JAX_RS_APPLICATION_SERVICE_PROPERTIES; +import static org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants.JAX_RS_NAME; + +import java.util.List; +import java.util.Map; + +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.core.Configuration; +import javax.ws.rs.core.Feature; +import javax.ws.rs.core.FeatureContext; + +import org.apache.shiro.mgt.DefaultSecurityManager; +import org.apache.shiro.realm.Realm; +import org.apache.shiro.web.jaxrs.ExceptionMapper; +import org.apache.shiro.web.jaxrs.ShiroFeature; +import org.apache.shiro.web.jaxrs.SubjectPrincipalRequestFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This type adds support for establishing Shiro Authentication in a JAX-RS container + * using native JAX-RS features, rather than relying on an external Servlet Container. + * As a result some common features of the ShiroFilter are not supported, but native + * JAX-RS containers (i.e. ones that don't support Servlets) are able to be used. + * + * <p> Where possible we reuse types from the {@link ShiroFeature}, and where these + * overlap with types used in authorization we must register carefully, as it is not + * allowed to register the same extension twice. Also the ShiroFeature does not make + * correct use of priorities when registering. This Feature therefore: + * + * <ul> + * <li>Avoids duplicate registrations with extension types that are also used in authorization.</li> + * <li>Uses priorities to indicate that these are authentication extensions</li> + * </ul> + * + */ +public class ShiroAuthenticationFeature implements Feature { + + private static final Logger _LOG = LoggerFactory.getLogger( + ShiroAuthenticationFeature.class); + + public static final String SESSION_COOKIE_NAME = "JAXRSSESSIONID"; + + private final List<Realm> realms; + + private final DefaultSecurityManager manager; + + public ShiroAuthenticationFeature(List<Realm> realms) { + this.realms = realms; + this.manager = realms.isEmpty() ? new DefaultSecurityManager() : new DefaultSecurityManager(realms); + } + + @Override + public boolean configure(FeatureContext fc) { + + Configuration configuration = fc.getConfiguration(); + + if(_LOG.isInfoEnabled()) { + @SuppressWarnings("unchecked") + Map<String, Object> applicationProps = (Map<String, Object>) configuration.getProperty(JAX_RS_APPLICATION_SERVICE_PROPERTIES); + _LOG.info("Registering the Shiro Authentication feature with application {}", + applicationProps.getOrDefault(JAX_RS_NAME, "<No Name found in application configuration>")); + } + + if(realms.isEmpty()) { + _LOG.warn("There are no authentication realms available. Users may not be able to authenticate."); + } else { + _LOG.debug("Using the authentication realms {}.", realms); + } + + _LOG.debug("Registering the Shiro SecurityManagerAssociatingFilter"); + fc.register(new SecurityManagerAssociatingFilter(manager), AUTHENTICATION); + + Map<Class<?>, Integer> contracts = configuration.getContracts(ExceptionMapper.class); + if(contracts.isEmpty()) { + _LOG.debug("Registering the Shiro ExceptionMapper"); + // Only register the ExceptionMapper if it isn't already registered + fc.register(ExceptionMapper.class, AUTHENTICATION); + } else if(AUTHENTICATION < contracts.getOrDefault(javax.ws.rs.ext.ExceptionMapper.class, USER)) { + _LOG.debug("Updating the priority of the Shiro ExceptionMapper from {} to {}", + contracts.getOrDefault(javax.ws.rs.ext.ExceptionMapper.class, USER), + AUTHORIZATION); + // Update the priority if it's registered too low + contracts.put(javax.ws.rs.ext.ExceptionMapper.class, AUTHENTICATION); + } + + contracts = configuration.getContracts(SubjectPrincipalRequestFilter.class); + if(contracts.isEmpty()) { + _LOG.debug("Registering the Shiro SubjectPrincipalRequestFilter"); + // Only register the SubjectPrincipalRequestFilter if it isn't already registered + // and make sure it always comes after the SecurityManagerAssociatingFilter + fc.register(SubjectPrincipalRequestFilter.class, AUTHENTICATION + 1); + } else if(AUTHENTICATION < contracts.getOrDefault(ContainerRequestFilter.class, USER)) { + _LOG.debug("Updating the priority of the Shiro SubjectPrincipalRequestFilter from {} to {}", + contracts.getOrDefault(ContainerRequestFilter.class, USER), + AUTHENTICATION + 1); + // Update the priority if it's registered too low + contracts.put(ContainerRequestFilter.class, AUTHENTICATION + 1); + } + + return true; + } + + public void close() { + manager.destroy(); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/aries-jax-rs-whiteboard/blob/16da1127/integrations/shiro/shiro-authc/src/main/java/org/apache/aries/jax/rs/shiro/authc/impl/ShiroAuthenticationFeatureProvider.java ---------------------------------------------------------------------- diff --git a/integrations/shiro/shiro-authc/src/main/java/org/apache/aries/jax/rs/shiro/authc/impl/ShiroAuthenticationFeatureProvider.java b/integrations/shiro/shiro-authc/src/main/java/org/apache/aries/jax/rs/shiro/authc/impl/ShiroAuthenticationFeatureProvider.java new file mode 100644 index 0000000..ae5bf42 --- /dev/null +++ b/integrations/shiro/shiro-authc/src/main/java/org/apache/aries/jax/rs/shiro/authc/impl/ShiroAuthenticationFeatureProvider.java @@ -0,0 +1,43 @@ +package org.apache.aries.jax.rs.shiro.authc.impl; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.ws.rs.core.Feature; + +import org.apache.shiro.realm.Realm; +import org.osgi.framework.Bundle; +import org.osgi.framework.PrototypeServiceFactory; +import org.osgi.framework.ServiceRegistration; + +public class ShiroAuthenticationFeatureProvider implements PrototypeServiceFactory<Feature> { + + private final List<Realm> realms; + + private final Set<ShiroAuthenticationFeature> features = Collections.synchronizedSet(new HashSet<>()); + + public ShiroAuthenticationFeatureProvider(List<Realm> realms) { + this.realms = realms; + } + + @Override + public ShiroAuthenticationFeature getService(Bundle bundle, ServiceRegistration<Feature> registration) { + ShiroAuthenticationFeature authenticationFeature = new ShiroAuthenticationFeature(realms); + features.add(authenticationFeature); + return authenticationFeature; + } + + @Override + public void ungetService(Bundle bundle, ServiceRegistration<Feature> registration, Feature service) { + if(features.remove(service)) { + ((ShiroAuthenticationFeature) service).close(); + } + } + + public void close() { + features.forEach(ShiroAuthenticationFeature::close); + } + +} http://git-wip-us.apache.org/repos/asf/aries-jax-rs-whiteboard/blob/16da1127/integrations/shiro/shiro-authz/bnd.bnd ---------------------------------------------------------------------- diff --git a/integrations/shiro/shiro-authz/bnd.bnd b/integrations/shiro/shiro-authz/bnd.bnd new file mode 100644 index 0000000..a29ec70 --- /dev/null +++ b/integrations/shiro/shiro-authz/bnd.bnd @@ -0,0 +1,27 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +# Ideally we would not include this, but for some reason the Shiro JAX-RS +# Support is not an OSGi bundle + +Export-Package: org.apache.shiro.web.jaxrs;version=${shiro.version} + +Conditional-Package: org.apache.aries.component.dsl.* + +-contract: * + +Bundle-Activator: org.apache.aries.jax.rs.shiro.authz.impl.ShiroAuthorizationActivator \ No newline at end of file http://git-wip-us.apache.org/repos/asf/aries-jax-rs-whiteboard/blob/16da1127/integrations/shiro/shiro-authz/pom.xml ---------------------------------------------------------------------- diff --git a/integrations/shiro/shiro-authz/pom.xml b/integrations/shiro/shiro-authz/pom.xml new file mode 100644 index 0000000..375350b --- /dev/null +++ b/integrations/shiro/shiro-authz/pom.xml @@ -0,0 +1,68 @@ +<?xml version='1.0' encoding='UTF-8' ?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.aries.jax.rs</groupId> + <artifactId>shiro-integration</artifactId> + <version>1.0.0-SNAPSHOT</version> + </parent> + + <artifactId>org.apache.aries.jax.rs.shiro.authz</artifactId> + <description>Apache Aries JAX-RS Shiro Authorization Provider</description> + <name>Apache Aries JAX-RS Shiro Authorization</name> + + <dependencies> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.core</artifactId> + <version>6.0.0</version> + </dependency> + <dependency> + <groupId>org.apache.aries.component-dsl</groupId> + <artifactId>org.apache.aries.component-dsl.component-dsl</artifactId> + <version>1.2.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.aries.spec</groupId> + <artifactId>org.apache.aries.javax.jax.rs-api</artifactId> + <version>1.0.0</version> + </dependency> + <dependency> + <groupId>org.apache.shiro</groupId> + <artifactId>shiro-jaxrs</artifactId> + <version>${shiro.version}</version> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.service.jaxrs</artifactId> + <version>1.0.0</version> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>biz.aQute.bnd</groupId> + <artifactId>bnd-maven-plugin</artifactId> + </plugin> + </plugins> + </build> + +</project> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/aries-jax-rs-whiteboard/blob/16da1127/integrations/shiro/shiro-authz/src/main/java/org/apache/aries/jax/rs/shiro/authz/impl/ShiroAuthorizationActivator.java ---------------------------------------------------------------------- diff --git a/integrations/shiro/shiro-authz/src/main/java/org/apache/aries/jax/rs/shiro/authz/impl/ShiroAuthorizationActivator.java b/integrations/shiro/shiro-authz/src/main/java/org/apache/aries/jax/rs/shiro/authz/impl/ShiroAuthorizationActivator.java new file mode 100644 index 0000000..06476df --- /dev/null +++ b/integrations/shiro/shiro-authz/src/main/java/org/apache/aries/jax/rs/shiro/authz/impl/ShiroAuthorizationActivator.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.aries.jax.rs.shiro.authz.impl; + +import static java.lang.Boolean.TRUE; +import static org.apache.aries.component.dsl.OSGi.coalesce; +import static org.apache.aries.component.dsl.OSGi.configuration; +import static org.apache.aries.component.dsl.OSGi.just; +import static org.apache.aries.component.dsl.OSGi.register; +import static org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants.JAX_RS_EXTENSION; + +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Map; + +import javax.ws.rs.core.Feature; + +import org.apache.aries.component.dsl.OSGiResult; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ShiroAuthorizationActivator implements BundleActivator { + + private static final Logger _LOG = LoggerFactory.getLogger( + ShiroAuthorizationActivator.class); + + private OSGiResult _registration; + + @Override + public void start(BundleContext context) throws Exception { + + _LOG.debug("Starting the Shiro JAX-RS Authorization Feature"); + + _registration = coalesce( + configuration("org.apache.aries.jax.rs.shiro.authorization"), + just(() -> { + + _LOG.debug("Using the default configuration for the Shiro JAX-RS Authorization Feature"); + + Dictionary<String, Object> properties = + new Hashtable<>(); + + properties.put( + Constants.SERVICE_PID, + "org.apache.aries.jax.rs.shiro.authorization"); + + return properties; + }) + ).map(this::filter) + .flatMap(p -> register(Feature.class, new ShiroAuthorizationFeature(), p)) + .run(context); + } + + Map<String, Object> filter(Dictionary<String, ?> props) { + Map<String, Object> serviceProps = new Hashtable<>(); + + Enumeration<String> keys = props.keys(); + while(keys.hasMoreElements()) { + String key = keys.nextElement(); + if(!key.startsWith(".")) { + serviceProps.put(key, props.get(key)); + } + } + + serviceProps.put(JAX_RS_EXTENSION, TRUE); + + _LOG.debug("Shiro JAX-RS Authorization Feature service properties are: {}", serviceProps); + return serviceProps; + } + + @Override + public void stop(BundleContext context) throws Exception { + _LOG.debug("Stopping the Shiro JAX-RS Authorization Feature"); + _registration.close(); + } +} http://git-wip-us.apache.org/repos/asf/aries-jax-rs-whiteboard/blob/16da1127/integrations/shiro/shiro-authz/src/main/java/org/apache/aries/jax/rs/shiro/authz/impl/ShiroAuthorizationFeature.java ---------------------------------------------------------------------- diff --git a/integrations/shiro/shiro-authz/src/main/java/org/apache/aries/jax/rs/shiro/authz/impl/ShiroAuthorizationFeature.java b/integrations/shiro/shiro-authz/src/main/java/org/apache/aries/jax/rs/shiro/authz/impl/ShiroAuthorizationFeature.java new file mode 100644 index 0000000..a0fd4f8 --- /dev/null +++ b/integrations/shiro/shiro-authz/src/main/java/org/apache/aries/jax/rs/shiro/authz/impl/ShiroAuthorizationFeature.java @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.aries.jax.rs.shiro.authz.impl; + +import static javax.ws.rs.Priorities.AUTHORIZATION; +import static javax.ws.rs.Priorities.USER; +import static org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants.JAX_RS_APPLICATION_SERVICE_PROPERTIES; +import static org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants.JAX_RS_NAME; + +import java.util.Map; + +import javax.ws.rs.Priorities; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.core.Configuration; +import javax.ws.rs.core.Feature; +import javax.ws.rs.core.FeatureContext; + +import org.apache.shiro.web.jaxrs.ExceptionMapper; +import org.apache.shiro.web.jaxrs.ShiroAnnotationFilterFeature; +import org.apache.shiro.web.jaxrs.ShiroFeature; +import org.apache.shiro.web.jaxrs.SubjectPrincipalRequestFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This type mirrors {@link ShiroFeature}, by registering an {@link ExceptionMapper}, + * {@link SubjectPrincipalRequestFilter} and {@link ShiroAnnotationFilterFeature}. + * + * <p>We cannot use the {@link ShiroFeature} directly because several of the extension + * types it registers are also used to enable authentication, and it is not allowed to + * register the same extension twice. Also the ShiroFeature does not make correct use + * of priorities when registering. This Feature therefore: + * + * <ul> + * <li>Avoids duplicate registrations with extension types that are also used in authentication.</li> + * <li>Uses priorities to indicate that these are authorization extensions</li> + * </ul> + * + */ +public class ShiroAuthorizationFeature implements Feature { + + private static final Logger _LOG = LoggerFactory.getLogger( + ShiroAuthorizationFeature.class); + + @Override + public boolean configure(FeatureContext fc) { + + Configuration configuration = fc.getConfiguration(); + + if(_LOG.isInfoEnabled()) { + @SuppressWarnings("unchecked") + Map<String, Object> applicationProps = (Map<String, Object>) configuration.getProperty(JAX_RS_APPLICATION_SERVICE_PROPERTIES); + _LOG.info("Registering the Shiro Authorization feature with application {}", + applicationProps.getOrDefault(JAX_RS_NAME, "<No Name found in application configuration>")); + } + + Map<Class<?>, Integer> contracts = configuration.getContracts(ExceptionMapper.class); + if(contracts.isEmpty()) { + _LOG.debug("Registering the Shiro ExceptionMapper"); + // Only register the ExceptionMapper if it isn't already registered + fc.register(ExceptionMapper.class, AUTHORIZATION); + } else if(AUTHORIZATION < contracts.getOrDefault(javax.ws.rs.ext.ExceptionMapper.class, USER)) { + _LOG.debug("Updating the priority of the Shiro ExceptionMapper from {} to {}", + contracts.getOrDefault(javax.ws.rs.ext.ExceptionMapper.class, USER), + AUTHORIZATION); + // Update the priority if it's registered too low + contracts.put(javax.ws.rs.ext.ExceptionMapper.class, AUTHORIZATION); + } + + contracts = configuration.getContracts(SubjectPrincipalRequestFilter.class); + if(contracts.isEmpty()) { + _LOG.debug("Registering the Shiro SubjectPrincipalRequestFilter"); + // Only register the SubjectPrincipalRequestFilter if it isn't already registered + fc.register(SubjectPrincipalRequestFilter.class, AUTHORIZATION); + } else if(AUTHORIZATION < contracts.getOrDefault(ContainerRequestFilter.class, USER)) { + _LOG.debug("Updating the priority of the Shiro SubjectPrincipalRequestFilter from {} to {}", + contracts.getOrDefault(ContainerRequestFilter.class, USER), + AUTHORIZATION); + // Update the priority if it's registered too low + contracts.put(ContainerRequestFilter.class, AUTHORIZATION); + } + + _LOG.debug("Registering the Shiro ShiroAnnotationFilterFeature"); + fc.register(ShiroAnnotationFilterFeature.class, Priorities.AUTHORIZATION); + return true; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/aries-jax-rs-whiteboard/blob/16da1127/integrations/shiro/shiro-itest/bnd.bnd ---------------------------------------------------------------------- diff --git a/integrations/shiro/shiro-itest/bnd.bnd b/integrations/shiro/shiro-itest/bnd.bnd new file mode 100644 index 0000000..25ba42e --- /dev/null +++ b/integrations/shiro/shiro-itest/bnd.bnd @@ -0,0 +1,22 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +Bundle-Description: Integration Test bundle for the JSON-P Whiteboard support + +Test-Cases: ${classes;CONCRETE;EXTENDS;test.types.TestHelper} + +Require-Capability: osgi.service;filter:="(objectClass=org.osgi.service.cm.ConfigurationAdmin)" http://git-wip-us.apache.org/repos/asf/aries-jax-rs-whiteboard/blob/16da1127/integrations/shiro/shiro-itest/itest.bndrun ---------------------------------------------------------------------- diff --git a/integrations/shiro/shiro-itest/itest.bndrun b/integrations/shiro/shiro-itest/itest.bndrun new file mode 100644 index 0000000..2de44aa --- /dev/null +++ b/integrations/shiro/shiro-itest/itest.bndrun @@ -0,0 +1,72 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +-standalone: ${.}/target/index.xml + +-runrequires: \ + osgi.identity;filter:='(osgi.identity=org.apache.aries.jax.rs.shiro.itest)',\ + osgi.identity;filter:='(osgi.identity=org.apache.aries.jax.rs.whiteboard)',\ + osgi.identity;filter:='(osgi.identity=ch.qos.logback.classic)',\ + osgi.identity;filter:='(osgi.identity=org.apache.aries.jax.rs.shiro.authc)',\ + osgi.identity;filter:='(osgi.identity=org.apache.aries.jax.rs.shiro.authz)',\ + bnd.identity;id='org.apache.felix.gogo.command',\ + bnd.identity;id='org.apache.felix.gogo.runtime',\ + bnd.identity;id='org.apache.felix.gogo.shell' + +-runfw: org.apache.felix.framework;version='[6.0.0,6.0.1]' + +-runtrace: true + +-runee: JavaSE-1.8 +-resolve.effective: resolve, active + +-runsystemcapabilities: ${native_capability} +-runproperties: \ + logback.configurationFile=file:${.}/src/test/resources/logback.xml,\ + org.apache.felix.http.host=localhost,\ + org.osgi.service.http.port=* +-runblacklist:\ + osgi.identity;filter:='(osgi.identity=org.osgi.compendium)',\ + osgi.identity;filter:='(osgi.identity=osgi.cmpn)' +-runbundles: \ + ch.qos.logback.classic;version='[1.2.3,1.2.4)',\ + ch.qos.logback.core;version='[1.2.3,1.2.4)',\ + org.apache.felix.configadmin;version='[1.8.14,1.8.15)',\ + org.apache.felix.eventadmin;version='[1.4.8,1.4.9)',\ + org.apache.felix.http.jetty;version='[3.4.0,3.4.1)',\ + org.apache.felix.http.servlet-api;version='[1.1.2,1.1.3)',\ + org.osgi.service.jaxrs;version='[1.0.0,1.0.1)',\ + org.osgi.util.function;version='[1.1.0,1.1.1)',\ + org.osgi.util.promise;version='[1.1.0,1.1.1)',\ + osgi.enroute.hamcrest.wrapper;version='[1.3.0,1.3.1)',\ + osgi.enroute.junit.wrapper;version='[4.12.0,4.12.1)',\ + slf4j.api;version='[1.7.25,1.7.26)',\ + org.apache.servicemix.specs.annotation-api-1.3;version='[1.3.0,1.3.1)',\ + org.apache.aries.jax.rs.shiro.authc;version='[1.0.0,1.0.1)',\ + org.apache.aries.jax.rs.shiro.authz;version='[1.0.0,1.0.1)',\ + org.apache.aries.jax.rs.shiro.itest;version='[1.0.0,1.0.1)',\ + org.apache.commons.configuration;version='[2.2.0,2.2.1)',\ + org.apache.commons.lang3;version='[3.6.0,3.6.1)',\ + org.apache.commons.logging;version='[1.2.0,1.2.1)',\ + org.apache.shiro.core;version='[1.4.0,1.4.1)',\ + org.apache.felix.gogo.command;version='[1.0.2,1.0.3)',\ + org.apache.felix.gogo.runtime;version='[1.1.0,1.1.1)',\ + org.apache.felix.gogo.shell;version='[1.1.0,1.1.1)',\ + org.apache.shiro.crypto.core;version='[1.4.0,1.4.1)',\ + org.apache.aries.javax.jax.rs-api;version='[1.0.0,1.0.1)',\ + org.apache.aries.jax.rs.whiteboard;version='[1.0.0,1.0.1)' +-include: -personal.bnd http://git-wip-us.apache.org/repos/asf/aries-jax-rs-whiteboard/blob/16da1127/integrations/shiro/shiro-itest/pom.xml ---------------------------------------------------------------------- diff --git a/integrations/shiro/shiro-itest/pom.xml b/integrations/shiro/shiro-itest/pom.xml new file mode 100644 index 0000000..7cd1d78 --- /dev/null +++ b/integrations/shiro/shiro-itest/pom.xml @@ -0,0 +1,208 @@ +<?xml version='1.0' encoding='UTF-8' ?> +<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor + license agreements. See the NOTICE file distributed with this work for additional + information regarding copyright ownership. The ASF licenses this file to + you 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. --> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.aries.jax.rs</groupId> + <artifactId>shiro-integration</artifactId> + <version>1.0.0-SNAPSHOT</version> + </parent> + + <artifactId>org.apache.aries.jax.rs.shiro.itest</artifactId> + <description>Apache Aries JAX-RS Shiro Integration Tests</description> + <name>Apache Aries JAX-RS Shiro Integration Tests</name> + + <properties> + <maven.deploy.skip>true</maven.deploy.skip> + <maven.install.skip>true</maven.install.skip> + </properties> + + <build> + <plugins> + <plugin> + <groupId>biz.aQute.bnd</groupId> + <artifactId>bnd-indexer-maven-plugin</artifactId> + <configuration> + <includeJar>true</includeJar> + <localURLs>REQUIRED</localURLs> + </configuration> + </plugin> + <plugin> + <groupId>biz.aQute.bnd</groupId> + <artifactId>bnd-maven-plugin</artifactId> + </plugin> + <plugin> + <groupId>biz.aQute.bnd</groupId> + <artifactId>bnd-resolver-maven-plugin</artifactId> + <configuration> + <failOnChanges>false</failOnChanges> + <bndruns> + <bndrun>itest.bndrun</bndrun> + </bndruns> + </configuration> + </plugin> + <plugin> + <groupId>biz.aQute.bnd</groupId> + <artifactId>bnd-testing-maven-plugin</artifactId> + <configuration> + <failOnChanges>true</failOnChanges> + <resolve>false</resolve> + <bndruns> + <bndrun>itest.bndrun</bndrun> + </bndruns> + </configuration> + </plugin> + </plugins> + </build> + <dependencies> + <dependency> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-classic</artifactId> + <version>1.2.3</version> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>osgi.core</artifactId> + <version>6.0.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>osgi.annotation</artifactId> + <version>7.0.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.shiro</groupId> + <artifactId>shiro-core</artifactId> + <version>${shiro.version}</version> + </dependency> + <dependency> + <groupId>org.apache.aries.spec</groupId> + <artifactId>org.apache.aries.javax.jax.rs-api</artifactId> + <version>${aries.jaxrs.version}</version> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.service.cm</artifactId> + <version>1.5.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.aries.jax.rs</groupId> + <artifactId>org.apache.aries.jax.rs.shiro.authc</artifactId> + <version>${project.version}</version> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>org.apache.aries.jax.rs</groupId> + <artifactId>org.apache.aries.jax.rs.shiro.authz</artifactId> + <version>${project.version}</version> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-configuration2</artifactId> + <version>2.2</version> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>org.apache.aries.jax.rs</groupId> + <artifactId>org.apache.aries.jax.rs.whiteboard</artifactId> + <version>${aries.jaxrs.version}</version> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.configadmin</artifactId> + <version>1.8.14</version> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.eventadmin</artifactId> + <version>1.4.8</version> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.http.jetty</artifactId> + <version>3.4.0</version> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.scr</artifactId> + <version>2.1.0</version> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.framework</artifactId> + <version>6.0.0</version> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.log</artifactId> + <version>1.0.1</version> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.service.jaxrs</artifactId> + <version>1.0.0</version> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.util.function</artifactId> + <version>1.1.0</version> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.util.promise</artifactId> + <version>1.1.0</version> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>osgi.enroute.junit.wrapper</artifactId> + <version>4.12.0</version> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>osgi.enroute.hamcrest.wrapper</artifactId> + <version>1.3.0</version> + </dependency> + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.gogo.runtime</artifactId> + <version>1.1.0</version> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.gogo.shell</artifactId> + <version>1.1.0</version> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.gogo.command</artifactId> + <version>1.0.2</version> + <scope>runtime</scope> + </dependency> + </dependencies> +</project> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/aries-jax-rs-whiteboard/blob/16da1127/integrations/shiro/shiro-itest/src/main/java/test/ShiroTest.java ---------------------------------------------------------------------- diff --git a/integrations/shiro/shiro-itest/src/main/java/test/ShiroTest.java b/integrations/shiro/shiro-itest/src/main/java/test/ShiroTest.java new file mode 100644 index 0000000..2ea3e83 --- /dev/null +++ b/integrations/shiro/shiro-itest/src/main/java/test/ShiroTest.java @@ -0,0 +1,192 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test; + +import static java.lang.Boolean.TRUE; +import static org.junit.Assert.assertEquals; +import static org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants.JAX_RS_EXTENSION_SELECT; + +import java.util.Hashtable; + +import javax.ws.rs.client.Invocation.Builder; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; + +import org.apache.shiro.realm.Realm; +import org.apache.shiro.realm.SimpleAccountRealm; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.osgi.annotation.bundle.Capability; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.cm.Configuration; + +import test.types.TestHelper; +import test.types.TestShiroAnnotations; + +@Capability(namespace="osgi.service", attribute="objectClass=org.apache.shiro.realm.Realm") +public class ShiroTest extends TestHelper { + + private Configuration authzConfig, authcConfig; + + SimpleAccountRealm realm; + + ServiceRegistration<Realm> reg; + + @Before + public void setupConfigs() throws Exception { + authzConfig = getConfigurationAdmin() + .getConfiguration("org.apache.aries.jax.rs.shiro.authorization"); + + Hashtable<String, Object> table = new Hashtable<>(); + table.put("shiro.authz", TRUE); + table.put(JAX_RS_EXTENSION_SELECT, "(shiro.authc=true)"); + authzConfig.update(table); + + authcConfig = getConfigurationAdmin() + .getConfiguration("org.apache.aries.jax.rs.shiro.authentication"); + + table = new Hashtable<>(); + table.put("shiro.authc", TRUE); + authcConfig.update(table); + + realm = new SimpleAccountRealm(); + + reg = bundleContext.registerService(Realm.class, realm, null); + + Thread.sleep(1000); + } + + @After + public void cleanUp() throws Exception { + authzConfig.delete(); + + authcConfig.delete(); + + reg.unregister(); + } + + @Test + public void testGuestNoAuthPresent() throws Exception { + WebTarget webTarget = createDefaultTarget().path("test/guest"); + + registerAddon(new TestShiroAnnotations(), JAX_RS_EXTENSION_SELECT, "(shiro.authz=true)"); + + assertEquals("Welcome Guest", webTarget.request().get(String.class)); + } + + @Test + public void testAuthenticatedNoAuthPresent() throws Exception { + WebTarget webTarget = createDefaultTarget().path("test/authenticated"); + + registerAddon(new TestShiroAnnotations(), JAX_RS_EXTENSION_SELECT, "(shiro.authz=true)"); + + assertEquals(Status.UNAUTHORIZED.getStatusCode(), webTarget.request().get().getStatus()); + } + + + @Test + public void testGuestAuthPresent() throws Exception { + + realm.addAccount("Bill", "Ben"); + + WebTarget target = createDefaultTarget(); + + registerAddon(new TestShiroAnnotations(), JAX_RS_EXTENSION_SELECT, "(shiro.authz=true)"); + + Response authenticate = target.path("security/authenticate").request() + .header("user", "Bill") + .header("password", "Ben") + .post(null); + assertEquals(Status.OK.getStatusCode(), authenticate.getStatus()); + + + Builder request = target.path("test/guest").request(); + authenticate.getCookies().values().forEach(c -> request.cookie(c.getName(), c.getValue())); + + assertEquals(Status.UNAUTHORIZED.getStatusCode(), request.get().getStatus()); + } + + @Test + public void testAuthenticatedAuthPresent() throws Exception { + + realm.addAccount("Bill", "Ben"); + + WebTarget target = createDefaultTarget(); + + registerAddon(new TestShiroAnnotations(), JAX_RS_EXTENSION_SELECT, "(shiro.authz=true)"); + + Response authenticate = target.path("security/authenticate").request() + .header("user", "Bill") + .header("password", "Ben") + .post(null); + assertEquals(Status.OK.getStatusCode(), authenticate.getStatus()); + + + Builder request = target.path("test/authenticated").request(); + authenticate.getCookies().values().forEach(c -> request.cookie(c.getName(), c.getValue())); + + assertEquals("Welcome Bill", request.get(String.class)); + } + + @Test + public void testRoleAuthPresent() throws Exception { + + realm.addAccount("Bill", "Ben"); + + WebTarget target = createDefaultTarget(); + + registerAddon(new TestShiroAnnotations(), JAX_RS_EXTENSION_SELECT, "(shiro.authz=true)"); + + Response authenticate = target.path("security/authenticate").request() + .header("user", "Bill") + .header("password", "Ben") + .post(null); + assertEquals(Status.OK.getStatusCode(), authenticate.getStatus()); + + + Builder request = target.path("test/admin").request(); + authenticate.getCookies().values().forEach(c -> request.cookie(c.getName(), c.getValue())); + + assertEquals(Status.FORBIDDEN.getStatusCode(), request.get().getStatus()); + } + + @Test + public void testRoleAuthWithRolePresent() throws Exception { + + realm.addAccount("Bill", "Ben", "admin"); + + WebTarget target = createDefaultTarget(); + + registerAddon(new TestShiroAnnotations(), JAX_RS_EXTENSION_SELECT, "(shiro.authz=true)"); + + Response authenticate = target.path("security/authenticate").request() + .header("user", "Bill") + .header("password", "Ben") + .post(null); + assertEquals(Status.OK.getStatusCode(), authenticate.getStatus()); + + + Builder request = target.path("test/admin").request(); + authenticate.getCookies().values().forEach(c -> request.cookie(c.getName(), c.getValue())); + + assertEquals("Welcome Admin", request.get(String.class)); + } + +} http://git-wip-us.apache.org/repos/asf/aries-jax-rs-whiteboard/blob/16da1127/integrations/shiro/shiro-itest/src/main/java/test/types/TestHelper.java ---------------------------------------------------------------------- diff --git a/integrations/shiro/shiro-itest/src/main/java/test/types/TestHelper.java b/integrations/shiro/shiro-itest/src/main/java/test/types/TestHelper.java new file mode 100644 index 0000000..0dc5652 --- /dev/null +++ b/integrations/shiro/shiro-itest/src/main/java/test/types/TestHelper.java @@ -0,0 +1,306 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.types; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.Application; + +import org.junit.After; +import org.junit.Before; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceFactory; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.cm.ConfigurationAdmin; +import org.osgi.service.jaxrs.client.SseEventSourceFactory; +import org.osgi.service.jaxrs.runtime.JaxrsServiceRuntime; +import org.osgi.service.jaxrs.runtime.dto.RuntimeDTO; +import org.osgi.util.tracker.ServiceTracker; + +import static org.junit.Assert.assertTrue; +import static org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants.JAX_RS_APPLICATION_BASE; +import static org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants.JAX_RS_EXTENSION; +import static org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants.JAX_RS_NAME; +import static org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants.JAX_RS_RESOURCE; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.function.Function; +import java.util.function.Predicate; + +public abstract class TestHelper { + + public static BundleContext bundleContext = + FrameworkUtil. + getBundle(TestHelper.class). + getBundleContext(); + + protected Collection<ServiceRegistration<?>> _registrations = + new ArrayList<>(); + protected ServiceTracker<JaxrsServiceRuntime, JaxrsServiceRuntime> + _runtimeTracker; + protected ServiceTracker<ClientBuilder, ClientBuilder> + _clientBuilderTracker; + protected JaxrsServiceRuntime _runtime; + protected ServiceReference<JaxrsServiceRuntime> _runtimeServiceReference; + + @After + public void tearDown() { + Iterator<ServiceRegistration<?>> iterator = _registrations.iterator(); + + while (iterator.hasNext()) { + ServiceRegistration<?> registration = iterator.next(); + + try { + registration.unregister(); + } + catch(Exception e) { + } + finally { + iterator.remove(); + } + } + + if (_runtimeTracker != null) { + _runtimeTracker.close(); + } + + _clientBuilderTracker.close(); + + _configurationAdminTracker.close(); + + _sseEventSourceFactoryTracker.close(); + } + + @Before + public void before() { + _clientBuilderTracker = new ServiceTracker<>( + bundleContext, ClientBuilder.class, null); + + _clientBuilderTracker.open(); + + _configurationAdminTracker = new ServiceTracker<>( + bundleContext, ConfigurationAdmin.class, null); + + _configurationAdminTracker.open(); + + _sseEventSourceFactoryTracker = new ServiceTracker<>( + bundleContext, SseEventSourceFactory.class, null); + + _sseEventSourceFactoryTracker.open(); + + _runtimeTracker = new ServiceTracker<>( + bundleContext, JaxrsServiceRuntime.class, null); + + _runtimeTracker.open(); + + try { + _runtime = _runtimeTracker.waitForService(15000L); + } + catch (InterruptedException e) { + throw new RuntimeException(e); + } + + _runtimeServiceReference = _runtimeTracker.getServiceReference(); + } + + private ServiceTracker<ConfigurationAdmin, ConfigurationAdmin> + _configurationAdminTracker; + + private ServiceTracker<SseEventSourceFactory, SseEventSourceFactory> + _sseEventSourceFactoryTracker; + + @SuppressWarnings("unchecked") + private static String[] canonicalize(Object propertyValue) { + if (propertyValue == null) { + return new String[0]; + } + if (propertyValue instanceof String[]) { + return (String[]) propertyValue; + } + if (propertyValue instanceof Collection) { + return ((Collection<String>) propertyValue).toArray(new String[0]); + } + + return new String[]{propertyValue.toString()}; + } + + public ConfigurationAdmin getConfigurationAdmin() { + + try { + return _configurationAdminTracker.waitForService(5000); + } + catch (InterruptedException e) { + throw new IllegalStateException(e); + } + } + + protected Client createClient() { + ClientBuilder clientBuilder; + + try { + clientBuilder = _clientBuilderTracker.waitForService(5000); + + return clientBuilder.build(); + } + catch (InterruptedException ie) { + throw new RuntimeException(ie); + } + } + + protected SseEventSourceFactory createSseFactory() { + try { + return _sseEventSourceFactoryTracker.waitForService(5000); + } + catch (InterruptedException ie) { + throw new RuntimeException(ie); + } + } + + protected WebTarget createDefaultTarget() { + Client client = createClient(); + + String[] runtimes = canonicalize( + _runtimeServiceReference.getProperty("osgi.jaxrs.endpoint")); + + if (runtimes.length == 0) { + throw new IllegalStateException( + "No runtimes could be found on \"osgi.jaxrs.endpoint\" " + + "runtime service property "); + } + + String runtime = runtimes[0]; + + return client.target(runtime); + } + + protected static long getServiceId(ServiceRegistration<?> propertyHolder) { + return (long)propertyHolder.getReference().getProperty("service.id"); + } + + protected <T> void assertThatInRuntime( + Function<RuntimeDTO, T[]> getter, Predicate<T> predicate) { + + assertTrue( + Arrays.stream(getter.apply(getRuntimeDTO())).anyMatch(predicate)); + } + + protected JaxrsServiceRuntime getJaxrsServiceRuntime() + throws InterruptedException { + + _runtimeTracker = new ServiceTracker<>( + bundleContext, JaxrsServiceRuntime.class, null); + + _runtimeTracker.open(); + + return _runtimeTracker.waitForService(15000L); + } + + protected RuntimeDTO getRuntimeDTO() { + return _runtime.getRuntimeDTO(); + } + + protected ServiceRegistration<?> registerAddon( + Object instance, Object... keyValues) { + + Dictionary<String, Object> properties = new Hashtable<>(); + + properties.put(JAX_RS_RESOURCE, "true"); + + for (int i = 0; i < keyValues.length; i = i + 2) { + properties.put(keyValues[i].toString(), keyValues[i + 1]); + } + + ServiceRegistration<Object> serviceRegistration = + bundleContext.registerService(Object.class, instance, properties); + + _registrations.add(serviceRegistration); + + return serviceRegistration; + } + + protected ServiceRegistration<Application> registerApplication( + Application application, Object... keyValues) { + + Dictionary<String, Object> properties = new Hashtable<>(); + + for (int i = 0; i < keyValues.length; i = i + 2) { + properties.put(keyValues[i].toString(), keyValues[i + 1]); + } + + if (properties.get(JAX_RS_APPLICATION_BASE) == null) { + properties.put(JAX_RS_APPLICATION_BASE, "/test-application"); + } + + ServiceRegistration<Application> serviceRegistration = + bundleContext.registerService( + Application.class, application, properties); + + _registrations.add(serviceRegistration); + + return serviceRegistration; + } + + protected ServiceRegistration<Application> registerApplication( + ServiceFactory<Application> serviceFactory, Object... keyValues) { + + Dictionary<String, Object> properties = new Hashtable<>(); + + properties.put(JAX_RS_APPLICATION_BASE, "/test-application"); + + for (int i = 0; i < keyValues.length; i = i + 2) { + properties.put(keyValues[i].toString(), keyValues[i + 1]); + } + + ServiceRegistration<Application> serviceRegistration = + bundleContext.registerService( + Application.class, serviceFactory, properties); + + _registrations.add(serviceRegistration); + + return serviceRegistration; + } + + protected <T> ServiceRegistration<T> registerExtension( + Class<T> clazz, T extension, String name, Object... keyValues) { + + Dictionary<String, Object> properties = new Hashtable<>(); + + properties.put(JAX_RS_EXTENSION, true); + properties.put(JAX_RS_NAME, name); + + for (int i = 0; i < keyValues.length; i = i + 2) { + properties.put(keyValues[i].toString(), keyValues[i + 1]); + } + + ServiceRegistration<T> serviceRegistration = + bundleContext.registerService(clazz, extension, properties); + + _registrations.add(serviceRegistration); + + return serviceRegistration; + } + +} http://git-wip-us.apache.org/repos/asf/aries-jax-rs-whiteboard/blob/16da1127/integrations/shiro/shiro-itest/src/main/java/test/types/TestShiroAnnotations.java ---------------------------------------------------------------------- diff --git a/integrations/shiro/shiro-itest/src/main/java/test/types/TestShiroAnnotations.java b/integrations/shiro/shiro-itest/src/main/java/test/types/TestShiroAnnotations.java new file mode 100644 index 0000000..6b21e45 --- /dev/null +++ b/integrations/shiro/shiro-itest/src/main/java/test/types/TestShiroAnnotations.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test.types; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.authz.annotation.RequiresAuthentication; +import org.apache.shiro.authz.annotation.RequiresGuest; +import org.apache.shiro.authz.annotation.RequiresRoles; + +@Path("/test") +public class TestShiroAnnotations { + + @GET + @Produces(MediaType.TEXT_PLAIN) + @Path("/guest") + @RequiresGuest + public String guest() { + return "Welcome Guest"; + } + + @GET + @Produces(MediaType.TEXT_PLAIN) + @Path("/authenticated") + @RequiresAuthentication + public String authenticated() { + return "Welcome " + SecurityUtils.getSubject().getPrincipal().toString(); + } + + + @GET + @Produces(MediaType.TEXT_PLAIN) + @Path("/admin") + @RequiresRoles("admin") + public String admin() { + return "Welcome Admin"; + } + +} http://git-wip-us.apache.org/repos/asf/aries-jax-rs-whiteboard/blob/16da1127/integrations/shiro/shiro-itest/src/test/resources/logback.xml ---------------------------------------------------------------------- diff --git a/integrations/shiro/shiro-itest/src/test/resources/logback.xml b/integrations/shiro/shiro-itest/src/test/resources/logback.xml new file mode 100644 index 0000000..b5db67b --- /dev/null +++ b/integrations/shiro/shiro-itest/src/test/resources/logback.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. +--> + +<configuration> + <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"> + <resetJUL>true</resetJUL> + </contextListener> + + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> + <encoder> + <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> + </encoder> + </appender> + + <root level="error"> + <appender-ref ref="STDOUT" /> + </root> + + <logger name="org.apache.aries.jax.rs" level="DEBUG"/> +</configuration> \ No newline at end of file
