aboutsummaryrefslogtreecommitdiffstats
path: root/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa
diff options
context:
space:
mode:
Diffstat (limited to 'odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa')
-rw-r--r--odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/Activator.java45
-rw-r--r--odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/ServiceProxy.java94
-rw-r--r--odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/accounting/Accounter.java38
-rw-r--r--odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/authorization/DefaultRBACRules.java78
-rw-r--r--odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/authorization/RBACRule.java170
-rw-r--r--odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/AAAFilter.java72
-rw-r--r--odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/MoonOAuthFilter.java187
-rw-r--r--odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/ODLHttpAuthenticationFilter.java78
-rw-r--r--odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/moon/MoonPrincipal.java155
-rw-r--r--odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/moon/MoonTokenEndpoint.java30
10 files changed, 947 insertions, 0 deletions
diff --git a/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/Activator.java b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/Activator.java
new file mode 100644
index 00000000..2f1c98f7
--- /dev/null
+++ b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/Activator.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.aaa.shiro;
+
+import org.apache.felix.dm.DependencyActivatorBase;
+import org.apache.felix.dm.DependencyManager;
+import org.osgi.framework.BundleContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This scaffolding allows the use of AAA Filters without AuthN or AuthZ
+ * enabled. This is done to support workflows such as those included in the
+ * <code>odl-restconf-noauth</code> feature.
+ *
+ * This class is also responsible for offering contextual <code>DEBUG</code>
+ * level clues concerning the activation of the <code>aaa-shiro</code> bundle.
+ * To enable these debug messages, issue the following command in the karaf
+ * shell: <code>log:set debug org.opendaylight.aaa.shiro.Activator</code>
+ *
+ * @author Ryan Goulding (ryandgoulding@gmail.com)
+ */
+public class Activator extends DependencyActivatorBase {
+
+ private static final Logger LOG = LoggerFactory.getLogger(Activator.class);
+
+ @Override
+ public void destroy(BundleContext bc, DependencyManager dm) throws Exception {
+ final String DEBUG_MESSAGE = "Destroying the aaa-shiro bundle";
+ LOG.debug(DEBUG_MESSAGE);
+ }
+
+ @Override
+ public void init(BundleContext bc, DependencyManager dm) throws Exception {
+ final String DEBUG_MESSAGE = "Initializing the aaa-shiro bundle";
+ LOG.debug(DEBUG_MESSAGE);
+ }
+
+}
diff --git a/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/ServiceProxy.java b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/ServiceProxy.java
new file mode 100644
index 00000000..e4485d73
--- /dev/null
+++ b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/ServiceProxy.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2016 Brocade Communications Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.aaa.shiro;
+
+import org.opendaylight.aaa.shiro.filters.AAAFilter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Responsible for enabling and disabling the AAA service. By default, the
+ * service is disabled; the AAAFilter will not require AuthN or AuthZ. The
+ * service is enabled through calling
+ * <code>ServiceProxy.getInstance().setEnabled(true)</code>. AuthN and AuthZ are
+ * disabled by default in order to support workflows such as the feature
+ * <code>odl-restconf-noauth</code>.
+ *
+ * The AAA service is enabled through installing the <code>odl-aaa-shiro</code>
+ * feature. The <code>org.opendaylight.aaa.shiroact.Activator()</code>
+ * constructor calls enables AAA through the ServiceProxy, which in turn enables
+ * the AAAFilter.
+ *
+ * ServiceProxy is a singleton; access to the ServiceProxy is granted through
+ * the <code>getInstance()</code> function.
+ *
+ * @author Ryan Goulding (ryandgoulding@gmail.com)
+ * @see <a
+ * href="https://github.com/opendaylight/netconf/blob/master/opendaylight/restconf/sal-rest-connector/src/main/resources/WEB-INF/web.xml">resconf
+ * web,xml</a>
+ * @see <code>org.opendaylight.aaa.shiro.Activator</code>
+ * @see <code>org.opendaylight.aaa.shiro.filters.AAAFilter</code>
+ */
+public class ServiceProxy {
+ private static final Logger LOG = LoggerFactory.getLogger(ServiceProxy.class);
+
+ /**
+ * AuthN and AuthZ are disabled by default to support workflows included in
+ * features such as <code>odl-restconf-noauth</code>
+ */
+ public static final boolean DEFAULT_AA_ENABLE_STATUS = false;
+
+ private static ServiceProxy instance = new ServiceProxy();
+ private volatile boolean enabled = false;
+ private AAAFilter filter;
+
+ /**
+ * private for singleton pattern
+ */
+ private ServiceProxy() {
+ final String INFO_MESSAGE = "Creating the ServiceProxy";
+ LOG.info(INFO_MESSAGE);
+ }
+
+ /**
+ * @return ServiceProxy, a feature level singleton
+ */
+ public static ServiceProxy getInstance() {
+ return instance;
+ }
+
+ /**
+ * Enables/disables the feature, cascading the state information to the
+ * AAAFilter.
+ *
+ * @param enabled A flag indicating whether to enable the Service.
+ */
+ public synchronized void setEnabled(final boolean enabled) {
+ this.enabled = enabled;
+ final String SERVICE_ENABLED_INFO_MESSAGE = "Setting ServiceProxy enabled to " + enabled;
+ LOG.info(SERVICE_ENABLED_INFO_MESSAGE);
+ // check for null because of non-determinism in bundle load
+ if (filter != null) {
+ filter.setEnabled(enabled);
+ }
+ }
+
+ /**
+ * Extract whether the service is enabled.
+ *
+ * @param filter
+ * register an optional Filter for callback if enable state
+ * changes
+ * @return Whether the service is enabled
+ */
+ public synchronized boolean getEnabled(final AAAFilter filter) {
+ this.filter = filter;
+ return enabled;
+ }
+}
diff --git a/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/accounting/Accounter.java b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/accounting/Accounter.java
new file mode 100644
index 00000000..e768ea59
--- /dev/null
+++ b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/accounting/Accounter.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.aaa.shiro.accounting;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Accounter is a common place to output AAA messages. Use this class through
+ * invoking <code>Logger.output("message")</code>.
+ *
+ * @author Ryan Goulding (ryandgoulding@gmail.com)
+ */
+public class Accounter {
+
+ private static final Logger LOG = LoggerFactory.getLogger(Accounter.class);
+
+ /*
+ * Essentially makes Accounter a singleton, avoiding the verbosity of
+ * <code>Accounter.getInstance().output("message")</code>.
+ */
+ private Accounter() {
+ }
+
+ /**
+ * Account for a particular <code>message</code>
+ *
+ * @param message A message for the aggregated AAA log.
+ */
+ public static void output(final String message) {
+ LOG.debug(message);
+ }
+}
diff --git a/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/authorization/DefaultRBACRules.java b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/authorization/DefaultRBACRules.java
new file mode 100644
index 00000000..9e84c988
--- /dev/null
+++ b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/authorization/DefaultRBACRules.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.aaa.shiro.authorization;
+
+import com.google.common.collect.Sets;
+import java.util.Collection;
+import java.util.HashSet;
+
+/**
+ * A singleton container of default authorization rules that are installed as
+ * part of Shiro initialization. This class defines an immutable set of rules
+ * that are needed to provide system-wide security. These include protecting
+ * certain MD-SAL leaf nodes that contain AAA data from random access. This is
+ * not a place to define your custom rule set; additional RBAC rules are
+ * configured through the shiro initialization file:
+ * <code>$KARAF_HOME/shiro.ini</code>
+ *
+ * An important distinction to consider is that Shiro URL rules work to protect
+ * the system at the Web layer, and <code>AuthzDomDataBroker</code> works to
+ * protect the system down further at the DOM layer.
+ *
+ * @author Ryan Goulding (ryandgoulding@gmail.com)
+ *
+ */
+public class DefaultRBACRules {
+
+ private static DefaultRBACRules instance;
+
+ /**
+ * a collection of the default security rules
+ */
+ private Collection<RBACRule> rbacRules = new HashSet<RBACRule>();
+
+ /**
+ * protects the AAA MD-SAL store by preventing access to the leaf nodes to
+ * non-admin users.
+ */
+ private static final RBACRule PROTECT_AAA_MDSAL = RBACRule.createAuthorizationRule(
+ "*/authorization/*", Sets.newHashSet("admin"));
+
+ /*
+ * private for singleton pattern
+ */
+ private DefaultRBACRules() {
+ // rbacRules.add(PROTECT_AAA_MDSAL);
+ }
+
+ /**
+ *
+ * @return the container instance for the default RBAC Rules
+ */
+ public static final DefaultRBACRules getInstance() {
+ if (null == instance) {
+ instance = new DefaultRBACRules();
+ }
+ return instance;
+ }
+
+ /**
+ *
+ * @return a copy of the default rules, so any modifications to the returned
+ * reference do not affect the <code>DefaultRBACRules</code>.
+ */
+ public final Collection<RBACRule> getRBACRules() {
+ // Returns a copy of the rbacRules set such that the original set keeps
+ // its contract of remaining immutable. Calls to rbacRules.add() are
+ // encapsulated solely in <code>DefaultRBACRules</code>.
+ //
+ // Since this method is only called at shiro initialiation time,
+ // memory consumption of creating a new set is a non-issue.
+ return Sets.newHashSet(rbacRules);
+ }
+}
diff --git a/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/authorization/RBACRule.java b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/authorization/RBACRule.java
new file mode 100644
index 00000000..0da95eb4
--- /dev/null
+++ b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/authorization/RBACRule.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.aaa.shiro.authorization;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Sets;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A container for RBAC Rules. An RBAC Rule is composed of a url pattern which
+ * may contain asterisk characters (*), and a collection of roles. These are
+ * represented in shiro.ini in the following format:
+ * <code>urlPattern=roles[atLeastOneCommaSeperatedRole]</code>
+ *
+ * RBACRules are immutable; that is, you cannot change the url pattern or the
+ * roles after creation. This is done for security purposes. RBACRules are
+ * created through utilizing a static factory method:
+ * <code>RBACRule.createRBACRule()</code>
+ *
+ * @author Ryan Goulding (ryandgoulding@gmail.com)
+ *
+ */
+public class RBACRule {
+
+ private static final Logger LOG = LoggerFactory.getLogger(RBACRule.class);
+
+ /**
+ * a url pattern that can optional contain asterisk characters (*)
+ */
+ private String urlPattern;
+
+ /**
+ * a collection of role names, such as "admin" and "user"
+ */
+ private Collection<String> roles = new HashSet<String>();
+
+ /**
+ * Creates an RBAC Rule. Made private for static factory method.
+ *
+ * @param urlPattern
+ * Cannot be null or the empty string.
+ * @param roles
+ * Must contain at least one role.
+ * @throws NullPointerException
+ * if <code>urlPattern</code> or <code>roles</code> is null
+ * @throws IllegalArgumentException
+ * if <code>urlPattern</code> is an empty string or
+ * <code>roles</code> is an empty collection.
+ */
+ private RBACRule(final String urlPattern, final Collection<String> roles)
+ throws NullPointerException, IllegalArgumentException {
+
+ this.setUrlPattern(urlPattern);
+ this.setRoles(roles);
+ }
+
+ /**
+ * The static factory method used to create RBACRules.
+ *
+ * @param urlPattern
+ * Cannot be null or the empty string.
+ * @param roles
+ * Cannot be null or an emtpy collection.
+ * @return An immutable RBACRule
+ */
+ public static RBACRule createAuthorizationRule(final String urlPattern,
+ final Collection<String> roles) {
+
+ RBACRule authorizationRule = null;
+ try {
+ authorizationRule = new RBACRule(urlPattern, roles);
+ } catch (Exception e) {
+ LOG.error("Cannot instantiate the AuthorizationRule", e);
+ }
+ return authorizationRule;
+ }
+
+ /**
+ *
+ * @return the urlPattern for the RBACRule
+ */
+ public String getUrlPattern() {
+ return urlPattern;
+ }
+
+ /*
+ * helper to ensure the url pattern is not the empty string
+ */
+ private static void checkUrlPatternLength(final String urlPattern)
+ throws IllegalArgumentException {
+
+ final String EXCEPTION_MESSAGE = "Empty String is not allowed for urlPattern";
+ if (urlPattern.isEmpty()) {
+ throw new IllegalArgumentException(EXCEPTION_MESSAGE);
+ }
+ }
+
+ private void setUrlPattern(final String urlPattern) throws NullPointerException,
+ IllegalArgumentException {
+
+ Preconditions.checkNotNull(urlPattern);
+ checkUrlPatternLength(urlPattern);
+ this.urlPattern = urlPattern;
+ }
+
+ /**
+ *
+ * @return a copy of the rule, so any modifications to the returned
+ * reference do not affect the immutable <code>RBACRule</code>.
+ */
+ public Collection<String> getRoles() {
+ // Returns a copy of the roles collection such that the original set
+ // keeps
+ // its contract of remaining immutable.
+ //
+ // Since this method is only called at shiro initialiation time,
+ // memory consumption of creating a new set is a non-issue.
+ return Sets.newHashSet(roles);
+ }
+
+ /*
+ * check to ensure the roles collection is not empty
+ */
+ private static void checkRolesCollectionSize(final Collection<String> roles)
+ throws IllegalArgumentException {
+
+ final String EXCEPTION_MESSAGE = "roles must contain at least 1 role";
+ if (roles.isEmpty()) {
+ throw new IllegalArgumentException(EXCEPTION_MESSAGE);
+ }
+ }
+
+ private void setRoles(final Collection<String> roles) throws NullPointerException,
+ IllegalArgumentException {
+
+ Preconditions.checkNotNull(roles);
+ checkRolesCollectionSize(roles);
+ this.roles = roles;
+ }
+
+ /**
+ * Generates a string representation of the <code>RBACRule</code> roles in
+ * shiro form.
+ *
+ * @return roles string representation in the form
+ * <code>roles[roleOne,roleTwo]</code>
+ */
+ public String getRolesInShiroFormat() {
+ final String ROLES_STRING = "roles";
+ return ROLES_STRING + Arrays.toString(roles.toArray());
+ }
+
+ /**
+ * Generates the string representation of the <code>RBACRule</code> in shiro
+ * form. For example: <code>urlPattern=roles[admin,user]</code>
+ */
+ @Override
+ public String toString() {
+ return String.format("%s=%s", urlPattern, getRolesInShiroFormat());
+ }
+}
diff --git a/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/AAAFilter.java b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/AAAFilter.java
new file mode 100644
index 00000000..b53588d8
--- /dev/null
+++ b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/AAAFilter.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.aaa.shiro.filters;
+
+import org.apache.shiro.web.servlet.ShiroFilter;
+import org.opendaylight.aaa.shiro.ServiceProxy;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The default AAA JAX-RS 1.X Web Filter. This class is also responsible for
+ * delivering debug information; to enable these debug statements, please issue
+ * the following in the karaf shell:
+ *
+ * <code>log:set debug org.opendaylight.aaa.shiro.filters.AAAFilter</code>
+ *
+ * @author Ryan Goulding (ryandgoulding@gmail.com)
+ * @see <code>javax.servlet.Filter</code>
+ * @see <code>org.apache.shiro.web.servlet.ShiroFilter</code>
+ */
+public class AAAFilter extends ShiroFilter {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AAAFilter.class);
+
+ public AAAFilter() {
+ super();
+ final String DEBUG_MESSAGE = "Creating the AAAFilter";
+ LOG.debug(DEBUG_MESSAGE);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * Adds context clues that aid in debugging. Also initializes the enable
+ * status to correspond with
+ * <code>ServiceProxy.getInstance.getEnabled()</code>.
+ *
+ * @see org.apache.shiro.web.servlet.ShiroFilter#init()
+ */
+ @Override
+ public void init() throws Exception {
+ super.init();
+ final String DEBUG_MESSAGE = "Initializing the AAAFilter";
+ LOG.debug(DEBUG_MESSAGE);
+ // sets the filter to the startup value. Because of non-determinism in
+ // bundle loading, this passes an instance of itself along so that if
+ // the
+ // enable status changes, then AAAFilter enable status is changed.
+ setEnabled(ServiceProxy.getInstance().getEnabled(this));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * Adds context clues to aid in debugging whether the filter is enabled.
+ *
+ * @see
+ * org.apache.shiro.web.servlet.OncePerRequestFilter#setEnabled(boolean)
+ */
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ final String DEBUG_MESSAGE = "Setting AAAFilter enabled to " + enabled;
+ LOG.debug(DEBUG_MESSAGE);
+ }
+}
diff --git a/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/MoonOAuthFilter.java b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/MoonOAuthFilter.java
new file mode 100644
index 00000000..06038c54
--- /dev/null
+++ b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/MoonOAuthFilter.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.aaa.shiro.filters;
+
+
+import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
+import static javax.servlet.http.HttpServletResponse.SC_CREATED;
+import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.oltu.oauth2.as.response.OAuthASResponse;
+import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
+import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
+import org.apache.oltu.oauth2.common.message.OAuthResponse;
+import org.apache.oltu.oauth2.common.message.types.TokenType;
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
+import org.opendaylight.aaa.AuthenticationBuilder;
+import org.opendaylight.aaa.ClaimBuilder;
+import org.opendaylight.aaa.api.Authentication;
+import org.opendaylight.aaa.api.Claim;
+import org.opendaylight.aaa.shiro.moon.MoonPrincipal;
+import org.opendaylight.aaa.sts.OAuthRequest;
+import org.opendaylight.aaa.sts.ServiceLocator;
+
+
+public class MoonOAuthFilter extends AuthenticatingFilter{
+
+ private static final String DOMAIN_SCOPE_REQUIRED = "Domain scope required";
+ private static final String NOT_IMPLEMENTED = "not_implemented";
+ private static final String UNAUTHORIZED = "unauthorized";
+ private static final String UNAUTHORIZED_CREDENTIALS = "Unauthorized: Login/Password incorrect";
+
+ static final String TOKEN_GRANT_ENDPOINT = "/token";
+ static final String TOKEN_REVOKE_ENDPOINT = "/revoke";
+ static final String TOKEN_VALIDATE_ENDPOINT = "/validate";
+
+ @Override
+ protected UsernamePasswordToken createToken(ServletRequest request, ServletResponse response) throws Exception {
+ // TODO Auto-generated method stub
+ HttpServletRequest httpRequest = (HttpServletRequest) request;
+ OAuthRequest oauthRequest = new OAuthRequest(httpRequest);
+ return new UsernamePasswordToken(oauthRequest.getUsername(),oauthRequest.getPassword());
+ }
+
+ @Override
+ protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
+ // TODO Auto-generated method stub
+ Subject currentUser = SecurityUtils.getSubject();
+ return executeLogin(request, response);
+ }
+
+ protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,
+ ServletRequest request, ServletResponse response) throws Exception {
+ HttpServletResponse httpResponse= (HttpServletResponse) response;
+ MoonPrincipal principal = (MoonPrincipal) subject.getPrincipals().getPrimaryPrincipal();
+ Claim claim = principal.principalToClaim();
+ oauthAccessTokenResponse(httpResponse,claim,"",principal.getToken());
+ return true;
+ }
+
+ protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e,
+ ServletRequest request, ServletResponse response) {
+ HttpServletResponse resp = (HttpServletResponse) response;
+ error(resp, SC_BAD_REQUEST, UNAUTHORIZED_CREDENTIALS);
+ return false;
+ }
+
+ protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
+ /**
+ * Here, we will call three functions depending on whether user wants to:
+ * create Token
+ * refresh token
+ * delete token
+ */
+ HttpServletRequest req= (HttpServletRequest) request;
+ HttpServletResponse resp = (HttpServletResponse) response;
+ try {
+ if (req.getServletPath().equals(TOKEN_GRANT_ENDPOINT)) {
+ UsernamePasswordToken token = createToken(request, response);
+ if (token == null) {
+ String msg = "A valid non-null AuthenticationToken " +
+ "must be created in order to execute a login attempt.";
+ throw new IllegalStateException(msg);
+ }
+ try {
+ Subject subject = getSubject(request, response);
+ subject.login(token);
+ return onLoginSuccess(token, subject, request, response);
+ } catch (AuthenticationException e) {
+ return onLoginFailure(token, e, request, response);
+ }
+ } else if (req.getServletPath().equals(TOKEN_REVOKE_ENDPOINT)) {
+ //deleteAccessToken(req, resp);
+ } else if (req.getServletPath().equals(TOKEN_VALIDATE_ENDPOINT)) {
+ //validateToken(req, resp);
+ }
+ } catch (AuthenticationException e) {
+ error(resp, SC_UNAUTHORIZED, e.getMessage());
+ } catch (OAuthProblemException oe) {
+ error(resp, oe);
+ } catch (Exception e) {
+ error(resp, e);
+ }
+ return false;
+ }
+
+ private void oauthAccessTokenResponse(HttpServletResponse resp, Claim claim, String clientId, String token)
+ throws OAuthSystemException, IOException {
+ if (claim == null) {
+ throw new AuthenticationException(UNAUTHORIZED);
+ }
+
+ // Cache this token...
+ Authentication auth = new AuthenticationBuilder(new ClaimBuilder(claim).setClientId(
+ clientId).build()).setExpiration(tokenExpiration()).build();
+ ServiceLocator.getInstance().getTokenStore().put(token, auth);
+
+ OAuthResponse r = OAuthASResponse.tokenResponse(SC_CREATED).setAccessToken(token)
+ .setTokenType(TokenType.BEARER.toString())
+ .setExpiresIn(Long.toString(auth.expiration()))
+ .buildJSONMessage();
+ write(resp, r);
+ }
+
+ private void write(HttpServletResponse resp, OAuthResponse r) throws IOException {
+ resp.setStatus(r.getResponseStatus());
+ PrintWriter pw = resp.getWriter();
+ pw.print(r.getBody());
+ pw.flush();
+ pw.close();
+ }
+
+ private long tokenExpiration() {
+ return ServiceLocator.getInstance().getTokenStore().tokenExpiration();
+ }
+
+ // Emit an error OAuthResponse with the given HTTP code
+ private void error(HttpServletResponse resp, int httpCode, String error) {
+ try {
+ OAuthResponse r = OAuthResponse.errorResponse(httpCode).setError(error)
+ .buildJSONMessage();
+ write(resp, r);
+ } catch (Exception e1) {
+ // Nothing to do here
+ }
+ }
+
+ private void error(HttpServletResponse resp, OAuthProblemException e) {
+ try {
+ OAuthResponse r = OAuthResponse.errorResponse(SC_BAD_REQUEST).error(e)
+ .buildJSONMessage();
+ write(resp, r);
+ } catch (Exception e1) {
+ // Nothing to do here
+ }
+ }
+
+ private void error(HttpServletResponse resp, Exception e) {
+ try {
+ OAuthResponse r = OAuthResponse.errorResponse(SC_INTERNAL_SERVER_ERROR)
+ .setError(e.getClass().getName())
+ .setErrorDescription(e.getMessage()).buildJSONMessage();
+ write(resp, r);
+ } catch (Exception e1) {
+ // Nothing to do here
+ }
+ }
+
+} \ No newline at end of file
diff --git a/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/ODLHttpAuthenticationFilter.java b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/ODLHttpAuthenticationFilter.java
new file mode 100644
index 00000000..90b0101e
--- /dev/null
+++ b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/ODLHttpAuthenticationFilter.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.aaa.shiro.filters;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.shiro.codec.Base64;
+import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
+import org.apache.shiro.web.util.WebUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Extends <code>BasicHttpAuthenticationFilter</code> to include ability to
+ * authenticate OAuth2 tokens, which is needed for backwards compatibility with
+ * <code>TokenAuthFilter</code>.
+ *
+ * This behavior is enabled by default for backwards compatibility. To disable
+ * OAuth2 functionality, just comment out the following line from the
+ * <code>etc/shiro.ini</code> file:
+ * <code>authcBasic = org.opendaylight.aaa.shiro.filters.ODLHttpAuthenticationFilter</code>
+ * then restart the karaf container.
+ *
+ * @author Ryan Goulding (ryandgoulding@gmail.com)
+ *
+ */
+public class ODLHttpAuthenticationFilter extends BasicHttpAuthenticationFilter {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ODLHttpAuthenticationFilter.class);
+
+ // defined in lower-case for more efficient string comparison
+ protected static final String BEARER_SCHEME = "bearer";
+
+ protected static final String OPTIONS_HEADER = "OPTIONS";
+
+ public ODLHttpAuthenticationFilter() {
+ super();
+ LOG.info("Creating the ODLHttpAuthenticationFilter");
+ }
+
+ @Override
+ protected String[] getPrincipalsAndCredentials(String scheme, String encoded) {
+ final String decoded = Base64.decodeToString(encoded);
+ // attempt to decode username/password; otherwise decode as token
+ if (decoded.contains(":")) {
+ return decoded.split(":");
+ }
+ return new String[] { encoded };
+ }
+
+ @Override
+ protected boolean isLoginAttempt(String authzHeader) {
+ final String authzScheme = getAuthzScheme().toLowerCase();
+ final String authzHeaderLowerCase = authzHeader.toLowerCase();
+ return authzHeaderLowerCase.startsWith(authzScheme)
+ || authzHeaderLowerCase.startsWith(BEARER_SCHEME);
+ }
+
+ @Override
+ protected boolean isAccessAllowed(ServletRequest request, ServletResponse response,
+ Object mappedValue) {
+ final HttpServletRequest httpRequest = WebUtils.toHttp(request);
+ final String httpMethod = httpRequest.getMethod();
+ if (OPTIONS_HEADER.equalsIgnoreCase(httpMethod)) {
+ return true;
+ } else {
+ return super.isAccessAllowed(httpRequest, response, mappedValue);
+ }
+ }
+}
diff --git a/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/moon/MoonPrincipal.java b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/moon/MoonPrincipal.java
new file mode 100644
index 00000000..a95b4e7f
--- /dev/null
+++ b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/moon/MoonPrincipal.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.aaa.shiro.moon;
+
+import com.google.common.collect.ImmutableSet;
+
+import java.io.Serializable;
+import java.util.Set;
+
+import org.opendaylight.aaa.api.Claim;
+
+public class MoonPrincipal {
+
+ private final String username;
+ private final String domain;
+ private final String userId;
+ private final Set<String> roles;
+ private final String token;
+
+
+ public MoonPrincipal(String username, String domain, String userId, Set<String> roles, String token) {
+ this.username = username;
+ this.domain = domain;
+ this.userId = userId;
+ this.roles = roles;
+ this.token = token;
+ }
+
+ public MoonPrincipal createODLPrincipal(String username, String domain,
+ String userId, Set<String> roles, String token) {
+
+ return new MoonPrincipal(username, domain, userId, roles,token);
+ }
+
+ public Claim principalToClaim (){
+ return new MoonClaim("", this.getUserId(), this.getUsername(), this.getDomain(), this.getRoles());
+ }
+
+ public String getUsername() {
+ return this.username;
+ }
+
+ public String getDomain() {
+ return this.domain;
+ }
+
+ public String getUserId() {
+ return this.userId;
+ }
+
+ public Set<String> getRoles() {
+ return this.roles;
+ }
+
+ public String getToken(){
+ return this.token;
+ }
+
+ public class MoonClaim implements Claim, Serializable {
+ private static final long serialVersionUID = -8115027645190209125L;
+ private int hashCode = 0;
+ private String clientId;
+ private String userId;
+ private String user;
+ private String domain;
+ private ImmutableSet<String> roles;
+
+ public MoonClaim(String clientId, String userId, String user, String domain, Set<String> roles) {
+ this.clientId = clientId;
+ this.userId = userId;
+ this.user = user;
+ this.domain = domain;
+ this.roles = ImmutableSet.<String> builder().addAll(roles).build();
+
+ if (userId.isEmpty() || user.isEmpty() || roles.isEmpty() || roles.contains("")) {
+ throw new IllegalStateException("The Claim is missing one or more of the required fields.");
+ }
+ }
+
+ @Override
+ public String clientId() {
+ return clientId;
+ }
+
+ @Override
+ public String userId() {
+ return userId;
+ }
+
+ @Override
+ public String user() {
+ return user;
+ }
+
+ @Override
+ public String domain() {
+ return domain;
+ }
+
+ @Override
+ public Set<String> roles() {
+ return roles;
+ }
+ public String getClientId() {
+ return clientId;
+ }
+
+ public void setClientId(String clientId) {
+ this.clientId = clientId;
+ }
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public void setUserId(String userId) {
+ this.userId = userId;
+ }
+
+ public String getUser() {
+ return user;
+ }
+
+ public void setUser(String user) {
+ this.user = user;
+ }
+
+ public String getDomain() {
+ return domain;
+ }
+
+ public void setDomain(String domain) {
+ this.domain = domain;
+ }
+
+ public ImmutableSet<String> getRoles() {
+ return roles;
+ }
+
+ public void setRoles(ImmutableSet<String> roles) {
+ this.roles = roles;
+ }
+
+ @Override
+ public String toString() {
+ return "clientId:" + clientId + "," + "userId:" + userId + "," + "userName:" + user
+ + "," + "domain:" + domain + "," + "roles:" + roles ;
+ }
+ }
+} \ No newline at end of file
diff --git a/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/moon/MoonTokenEndpoint.java b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/moon/MoonTokenEndpoint.java
new file mode 100644
index 00000000..a954a606
--- /dev/null
+++ b/odl-aaa-moon/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/moon/MoonTokenEndpoint.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2015 Brocade Communications Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.aaa.shiro.moon;
+
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class MoonTokenEndpoint extends HttpServlet{
+
+ private static final long serialVersionUID = 4980356362831585417L;
+ private static final Logger LOG = LoggerFactory.getLogger(MoonTokenEndpoint.class);
+
+ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+ LOG.debug("MoonTokenEndpoint Servlet doPost");
+ }
+
+} \ No newline at end of file