summaryrefslogtreecommitdiffstats
path: root/upstream/odl-aaa-moon/aaa/aaa-shiro/src
diff options
context:
space:
mode:
Diffstat (limited to 'upstream/odl-aaa-moon/aaa/aaa-shiro/src')
-rw-r--r--upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/Activator.java45
-rw-r--r--upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/ServiceProxy.java94
-rw-r--r--upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/accounting/Accounter.java38
-rw-r--r--upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/authorization/DefaultRBACRules.java78
-rw-r--r--upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/authorization/RBACRule.java170
-rw-r--r--upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/AAAFilter.java72
-rw-r--r--upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/AAAShiroFilter.java51
-rw-r--r--upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/AuthenticationListener.java52
-rw-r--r--upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/AuthenticationTokenUtils.java129
-rw-r--r--upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/MoonOAuthFilter.java186
-rw-r--r--upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/ODLHttpAuthenticationFilter.java78
-rw-r--r--upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/moon/MoonPrincipal.java160
-rw-r--r--upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/moon/MoonTokenEndpoint.java30
-rw-r--r--upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/realm/MoonRealm.java99
-rw-r--r--upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/realm/ODLJndiLdapRealm.java315
-rw-r--r--upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/realm/ODLJndiLdapRealmAuthNOnly.java102
-rw-r--r--upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/realm/RadiusRealm.java37
-rw-r--r--upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/realm/TACACSRealm.java38
-rw-r--r--upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/realm/TokenAuthRealm.java369
-rw-r--r--upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/web/env/KarafIniWebEnvironment.java125
-rw-r--r--upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/resources/WEB-INF/web.xml48
-rw-r--r--upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/resources/shiro.ini106
-rw-r--r--upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/ServiceProxyTest.java45
-rw-r--r--upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/TestAppender.java67
-rw-r--r--upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/authorization/DefaultRBACRulesTest.java43
-rw-r--r--upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/authorization/RBACRuleTest.java106
-rw-r--r--upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/filters/AuthenticationListenerTest.java72
-rw-r--r--upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/filters/AuthenticationTokenUtilsTest.java124
-rw-r--r--upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/realm/ODLJndiLdapRealmTest.java246
-rw-r--r--upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/realm/TokenAuthRealmTest.java139
-rw-r--r--upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/web/env/KarafIniWebEnvironmentTest.java76
-rw-r--r--upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/resources/logback-test.xml21
32 files changed, 3361 insertions, 0 deletions
diff --git a/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/Activator.java b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/Activator.java
new file mode 100644
index 00000000..2f1c98f7
--- /dev/null
+++ b/upstream/odl-aaa-moon/aaa/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/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/ServiceProxy.java b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/ServiceProxy.java
new file mode 100644
index 00000000..e4485d73
--- /dev/null
+++ b/upstream/odl-aaa-moon/aaa/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/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/accounting/Accounter.java b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/accounting/Accounter.java
new file mode 100644
index 00000000..e768ea59
--- /dev/null
+++ b/upstream/odl-aaa-moon/aaa/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/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/authorization/DefaultRBACRules.java b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/authorization/DefaultRBACRules.java
new file mode 100644
index 00000000..9e84c988
--- /dev/null
+++ b/upstream/odl-aaa-moon/aaa/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/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/authorization/RBACRule.java b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/authorization/RBACRule.java
new file mode 100644
index 00000000..0da95eb4
--- /dev/null
+++ b/upstream/odl-aaa-moon/aaa/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/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/AAAFilter.java b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/AAAFilter.java
new file mode 100644
index 00000000..47dd9549
--- /dev/null
+++ b/upstream/odl-aaa-moon/aaa/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 RESTCONF 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/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/AAAShiroFilter.java b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/AAAShiroFilter.java
new file mode 100644
index 00000000..530acfac
--- /dev/null
+++ b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/AAAShiroFilter.java
@@ -0,0 +1,51 @@
+/*
+ * 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.filters;
+
+import org.apache.shiro.web.servlet.ShiroFilter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The default AAA JAX-RS 1.X Web Filter. Unlike AAAFilter, which is aimed towards
+ * supporting RESTCONF and its existing API mechanisms, AAAShiroFilter is a generic
+ * <code>ShiroFilter</code> for use with any other ODL Servlets. The main difference
+ * is that <code>AAAFilter</code> was designed to support the existing noauth
+ * mechanism, while this filter cannot be disabled.
+ *
+ * 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.AAAShiroFilter</code>
+ *
+ * @author Ryan Goulding (ryandgoulding@gmail.com)
+ * @see <code>javax.servlet.Filter</code>
+ * @see <code>org.apache.shiro.web.servlet.ShiroFilter</code>
+ */
+public class AAAShiroFilter extends ShiroFilter {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AAAShiroFilter.class);
+
+ public AAAShiroFilter() {
+ LOG.debug("Creating the AAAShiroFilter");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * Adds context clues that aid in debugging.
+ *
+ * @see org.apache.shiro.web.servlet.ShiroFilter#init()
+ */
+ @Override
+ public void init() throws Exception {
+ super.init();
+ LOG.debug("Initializing the AAAShiroFilter");
+ }
+}
diff --git a/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/AuthenticationListener.java b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/AuthenticationListener.java
new file mode 100644
index 00000000..080ab114
--- /dev/null
+++ b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/AuthenticationListener.java
@@ -0,0 +1,52 @@
+/*
+ * 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.filters;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Follows the event-listener pattern; the <code>Authenticator</code> notifies this class about
+ * authentication attempts. <code>AuthenticationListener</code> logs successful and unsuccessful
+ * authentication attempts appropriately. Log messages are emitted at the <code>DEBUG</code> log
+ * level. To enable the messages out of the box, use the following command from karaf:
+ * <code>log:set DEBUG org.opendaylight.aaa.shiro.authc.AuthenicationListener</code>
+ *
+ * @author Ryan Goulding (ryandgoulding@gmail.com)
+ */
+public class AuthenticationListener implements org.apache.shiro.authc.AuthenticationListener {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AuthenticationListener.class);
+
+ @Override
+ public void onSuccess(final AuthenticationToken authenticationToken, final AuthenticationInfo authenticationInfo) {
+ if (LOG.isDebugEnabled()) {
+ final String successMessage = AuthenticationTokenUtils.generateSuccessfulAuthenticationMessage(authenticationToken);
+ LOG.debug(successMessage);
+ }
+ }
+
+ @Override
+ public void onFailure(final AuthenticationToken authenticationToken, final AuthenticationException e) {
+ if (LOG.isDebugEnabled()) {
+ final String failureMessage = AuthenticationTokenUtils.generateUnsuccessfulAuthenticationMessage(authenticationToken);
+ LOG.debug(failureMessage);
+ }
+ }
+
+ @Override
+ public void onLogout(final PrincipalCollection principalCollection) {
+ // Do nothing; AAA is aimed at RESTCONF, which stateless by definition.
+ // Including this output would very quickly pollute the log.
+ }
+}
diff --git a/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/AuthenticationTokenUtils.java b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/AuthenticationTokenUtils.java
new file mode 100644
index 00000000..a5f0c10d
--- /dev/null
+++ b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/AuthenticationTokenUtils.java
@@ -0,0 +1,129 @@
+/*
+ * 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.filters;
+
+import com.google.common.base.Preconditions;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.UsernamePasswordToken;
+
+/**
+ * Utility methods for forming audit trail output based on an <code>AuthenticationToken</code>.
+ *
+ * @author Ryan Goulding (ryandgoulding@gmail.com)
+ */
+public class AuthenticationTokenUtils {
+
+ /**
+ * default value used in messaging when the "user" field is unparsable from the HTTP REST request
+ */
+ static final String DEFAULT_USERNAME = "an unknown user";
+
+ /**
+ * default value used in messaging when the "user" field is not present in the HTTP REST request, implying
+ * a different implementation of <code>AuthenticationToken</code> such as <code>CasToken</code>.
+ */
+ static final String DEFAULT_TOKEN = "an un-parsable token type";
+
+ /**
+ * default value used in messaging when the "host" field cannot be determined.
+ */
+ static final String DEFAULT_HOSTNAME = "an unknown host";
+
+ private AuthenticationTokenUtils() {
+ // private to prevent instantiation
+ }
+
+ /**
+ * Determines whether the supplied <code>Token</code> is a <code>UsernamePasswordToken</code>.
+ *
+ * @param token A generic <code>Token</code>, which might be a <code>UsernamePasswordToken</code>
+ * @return Whether the supplied <code>Token</code> is a <code>UsernamePasswordToken</code>
+ */
+ public static boolean isUsernamePasswordToken(final AuthenticationToken token) {
+ return token instanceof UsernamePasswordToken;
+ }
+
+ /**
+ * Extracts the username if possible. If the supplied token is a <code>UsernamePasswordToken</code>
+ * and the username field is not set, <code>DEFAULT_USERNAME</code> is returned. If the supplied
+ * token is not a <code>UsernamePasswordToken</code> (i.e., a <code>CasToken</code> or other
+ * implementation of <code>AuthenticationToken</code>), then <code>DEFAULT_TOKEN</code> is
+ * returned.
+ *
+ * @param token An <code>AuthenticationToken</code>, possibly a <code>UsernamePasswordToken</code>
+ * @return the username, <code>DEFAULT_USERNAME</code> or <code>DEFAULT_TOKEN</code> depending on input
+ */
+ public static String extractUsername(final AuthenticationToken token) {
+ if (isUsernamePasswordToken(token)) {
+ final UsernamePasswordToken upt = (UsernamePasswordToken) token;
+ return extractField(upt.getUsername(), DEFAULT_USERNAME);
+ }
+ return DEFAULT_TOKEN;
+ }
+
+ /**
+ * Extracts the hostname if possible. If the supplied token is a <code>UsernamePasswordToken</code>
+ * and the hostname field is not set, <code>DEFAULT_HOSTNAME</code> is returned. If the supplied
+ * token is not a <code>UsernamePasswordToken</code> (i.e., a <code>CasToken</code> or other
+ * implementation of <code>AuthenticationToken</code>), then <code>DEFAULT_HOSTNAME</code> is
+ * returned.
+ *
+ * @param token An <code>AuthenticationToken</code>, possibly a <code>UsernamePasswordToken</code>
+ * @return the hostname, or <code>DEFAULT_USERNAME</code> depending on input
+ */
+ public static String extractHostname(final AuthenticationToken token) {
+ if (isUsernamePasswordToken(token)) {
+ final UsernamePasswordToken upt = (UsernamePasswordToken) token;
+ return extractField(upt.getHost(), DEFAULT_HOSTNAME);
+ }
+ return DEFAULT_HOSTNAME;
+ }
+
+ /**
+ * Utility method to generate a generic message indicating Authentication was unsuccessful.
+ *
+ * @param token An <code>AuthenticationToken</code>, possibly a <code>UsernamePasswordToken</code>
+ * @return A message indicating authentication was unsuccessful
+ */
+ public static String generateUnsuccessfulAuthenticationMessage(final AuthenticationToken token) {
+ final String username = extractUsername(token);
+ final String remoteHostname = extractHostname(token);
+ return String.format("Unsuccessful authentication attempt by %s from %s", username, remoteHostname);
+ }
+
+ /**
+ * Utility method to generate a generic message indicating Authentication was successful.
+ *
+ * @param token An <code>AuthenticationToken</code>, possibly a <code>UsernamePasswordToken</code>
+ * @return A message indicating authentication was successful
+ */
+ public static String generateSuccessfulAuthenticationMessage(final AuthenticationToken token) {
+ final String username = extractUsername(token);
+ final String remoteHostname = extractHostname(token);
+ return String.format("Successful authentication attempt by %s from %s", username, remoteHostname);
+ }
+
+ /**
+ * Utility method that returns <code>field</code>, or <code>defaultValue</code> if <code>field</code> is null.
+ *
+ * @param field A generic string, which is possibly null.
+ * @param defaultValue A non-null value returned if <code>field</code> is null
+ * @return <code>field</code> or <code>defaultValue</code> if field is null
+ * @throws IllegalArgumentException If <code>defaultValue</code> is null
+ */
+ private static String extractField(final String field, final String defaultValue)
+ throws IllegalArgumentException {
+
+ Preconditions.checkNotNull(defaultValue, "defaultValue can't be null");
+ if (field != null) {
+ return field;
+ }
+ return defaultValue;
+ }
+}
diff --git a/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/MoonOAuthFilter.java b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/MoonOAuthFilter.java
new file mode 100644
index 00000000..241b7c28
--- /dev/null
+++ b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/MoonOAuthFilter.java
@@ -0,0 +1,186 @@
+/*
+ * 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 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;
+
+/**
+ * MoonOAuthFilter filters oauth1 requests form token based authentication
+ * @author Alioune BA alioune.ba@orange.com
+ *
+ */
+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 {
+
+ 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)) {
+ //TODO: deleteAccessToken(req, resp);
+ } else if (req.getServletPath().equals(TOKEN_VALIDATE_ENDPOINT)) {
+ //TODO: 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
+ }
+ }
+
+}
diff --git a/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/ODLHttpAuthenticationFilter.java b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/filters/ODLHttpAuthenticationFilter.java
new file mode 100644
index 00000000..90b0101e
--- /dev/null
+++ b/upstream/odl-aaa-moon/aaa/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/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/moon/MoonPrincipal.java b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/moon/MoonPrincipal.java
new file mode 100644
index 00000000..9dd2fd4f
--- /dev/null
+++ b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/moon/MoonPrincipal.java
@@ -0,0 +1,160 @@
+/*
+ * 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;
+
+/**
+ * MoonPrincipal contains all user's information returned by moon on successful authentication
+ * @author Alioune BA alioune.ba@orange.com
+ *
+ */
+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/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/moon/MoonTokenEndpoint.java b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/moon/MoonTokenEndpoint.java
new file mode 100644
index 00000000..a954a606
--- /dev/null
+++ b/upstream/odl-aaa-moon/aaa/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
diff --git a/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/realm/MoonRealm.java b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/realm/MoonRealm.java
new file mode 100644
index 00000000..9ebbb4d7
--- /dev/null
+++ b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/realm/MoonRealm.java
@@ -0,0 +1,99 @@
+/*
+ * 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.realm;
+
+import com.sun.jersey.api.client.Client;
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.WebResource;
+import com.sun.jersey.api.client.config.ClientConfig;
+import com.sun.jersey.api.client.config.DefaultClientConfig;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.SimpleAuthenticationInfo;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.realm.AuthorizingRealm;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.json.JSONTokener;
+import org.opendaylight.aaa.shiro.moon.MoonPrincipal;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+/**
+ * MoonRealm is a Shiro Realm that authenticates users from OPNFV/moon platform
+ * @author Alioune BA alioune.ba@orange.com
+ *
+ */
+public class MoonRealm extends AuthorizingRealm{
+
+ private static final Logger LOG = LoggerFactory.getLogger(MoonRealm.class);
+ @Override
+ protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
+ // TODO Auto-generated method stub
+ String username = "";
+ String password = "";
+ String domain = "sdn";
+ username = (String) authenticationToken.getPrincipal();
+ final UsernamePasswordToken upt = (UsernamePasswordToken) authenticationToken;
+ password = new String(upt.getPassword());
+ final MoonPrincipal moonPrincipal = moonAuthenticate(username,password,domain);
+ if (moonPrincipal!=null){
+ return new SimpleAuthenticationInfo(moonPrincipal, password.toCharArray(),getName());
+ }else{
+ return null;
+ }
+ }
+
+ public MoonPrincipal moonAuthenticate(String username, String password, String domain){
+
+ String output = "";
+ ClientConfig config = new DefaultClientConfig();
+ Client client = Client.create(config);
+ JSONTokener tokener;
+ JSONObject object =null;
+ Set<String> UserRoles = new LinkedHashSet<>();
+
+ String server = System.getenv("MOON_SERVER_ADDR");
+ String port = System.getenv("MOON_SERVER_PORT");
+ String URL = "http://" +server+ ":" +port+ "/moon/auth/tokens";
+ LOG.debug("Moon server is at: {} ", server);
+ WebResource webResource = client.resource(URL);
+ String input = "{\"username\": \""+ username + "\"," + "\"password\":" + "\"" + password + "\"," + "\"project\":" + "\"" + domain + "\"" + "}";;
+ ClientResponse response = webResource.type("application/json").post(ClientResponse.class, input);
+ output = response.getEntity(String.class);
+ tokener = new JSONTokener(output);
+ object = new JSONObject(tokener);
+ try {
+ if (object.getString("token")!=null){
+ String token = object.getString("token");
+ String userID = username+"@"+domain;
+ for (int i=0; i< object.getJSONArray("roles").length(); i++){
+ UserRoles.add((String) object.getJSONArray("roles").get(i));
+ }
+ MoonPrincipal principal = new MoonPrincipal(username,domain,userID,UserRoles,token);
+ return principal;
+ }
+ }catch (JSONException e){
+ throw new IllegalStateException("Authentication Error : "+ object.getJSONObject("error").getString("title"));
+ }
+ return null;
+ }
+
+}
diff --git a/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/realm/ODLJndiLdapRealm.java b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/realm/ODLJndiLdapRealm.java
new file mode 100644
index 00000000..7d0bafd7
--- /dev/null
+++ b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/realm/ODLJndiLdapRealm.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (c) 2015, 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.realm;
+
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import javax.naming.ldap.LdapContext;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.authz.SimpleAuthorizationInfo;
+import org.apache.shiro.realm.ldap.JndiLdapRealm;
+import org.apache.shiro.realm.ldap.LdapContextFactory;
+import org.apache.shiro.realm.ldap.LdapUtils;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.util.Nameable;
+import org.opendaylight.aaa.shiro.accounting.Accounter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * An extended implementation of
+ * <code>org.apache.shiro.realm.ldap.JndiLdapRealm</code> which includes
+ * additional Authorization capabilities. To enable this Realm, add the
+ * following to <code>shiro.ini</code>:
+ *
+ *<code>#ldapRealm = org.opendaylight.aaa.shiro.realm.ODLJndiLdapRealmAuthNOnly
+ *#ldapRealm.userDnTemplate = uid={0},ou=People,dc=DOMAIN,dc=TLD
+ *#ldapRealm.contextFactory.url = ldap://URL:389
+ *#ldapRealm.searchBase = dc=DOMAIN,dc=TLD
+ *#ldapRealm.ldapAttributeForComparison = objectClass
+ *# The CSV list of enabled realms. In order to enable a realm, add it to the
+ *# list below:
+ * securityManager.realms = $tokenAuthRealm, $ldapRealm</code>
+ *
+ * The values above are specific to the deployed LDAP domain. If the defaults
+ * are not sufficient, alternatives can be derived through enabling
+ * <code>TRACE</code> level logging. To enable <code>TRACE</code> level
+ * logging, issue the following command in the karaf shell:
+ * <code>log:set TRACE org.opendaylight.aaa.shiro.realm.ODLJndiLdapRealm</code>
+ *
+ * @author Ryan Goulding (ryandgoulding@gmail.com)
+ * @see <code>org.apache.shiro.realm.ldap.JndiLdapRealm</code>
+ * @see <a
+ * href="https://shiro.apache.org/static/1.2.3/apidocs/org/apache/shiro/realm/ldap/JndiLdapRealm.html">Shiro
+ * documentation</a>
+ */
+public class ODLJndiLdapRealm extends JndiLdapRealm implements Nameable {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ODLJndiLdapRealm.class);
+
+ /**
+ * When an LDAP Authorization lookup is made for a user account, a list of
+ * attributes are returned. The attributes are used to determine LDAP
+ * grouping, which is equivalent to ODL role(s). The default value is
+ * set to "objectClass", which is common attribute for LDAP systems.
+ * The actual value may be configured through setting
+ * <code>ldapAttributeForComparison</code>.
+ */
+ private static final String DEFAULT_LDAP_ATTRIBUTE_FOR_COMPARISON = "objectClass";
+
+ /**
+ * The LDAP nomenclature for user ID, which is used in the authorization query process.
+ */
+ private static final String UID = "uid";
+
+ /**
+ * The searchBase for the ldap query, which indicates the LDAP realms to
+ * search. By default, this is set to the
+ * <code>super.getUserDnSuffix()</code>.
+ */
+ private String searchBase = super.getUserDnSuffix();
+
+ /**
+ * When an LDAP Authorization lookup is made for a user account, a list of
+ * attributes is returned. The attributes are used to determine LDAP
+ * grouping, which is equivalent to ODL role(s). The default is set to
+ * <code>DEFAULT_LDAP_ATTRIBUTE_FOR_COMPARISON</code>.
+ */
+ private String ldapAttributeForComparison = DEFAULT_LDAP_ATTRIBUTE_FOR_COMPARISON;
+
+ /*
+ * Adds debugging information surrounding creation of ODLJndiLdapRealm
+ */
+ public ODLJndiLdapRealm() {
+ super();
+ final String DEBUG_MESSAGE = "Creating ODLJndiLdapRealm";
+ LOG.debug(DEBUG_MESSAGE);
+ }
+
+ /*
+ * (non-Javadoc) Overridden to expose important audit trail information for
+ * accounting.
+ *
+ * @see
+ * org.apache.shiro.realm.ldap.JndiLdapRealm#doGetAuthenticationInfo(org
+ * .apache.shiro.authc.AuthenticationToken)
+ */
+ @Override
+ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
+ throws AuthenticationException {
+
+ // Delegates all AuthN lookup responsibility to the super class
+ try {
+ final String username = getUsername(token);
+ logIncomingConnection(username);
+ return super.doGetAuthenticationInfo(token);
+ } catch (ClassCastException e) {
+ LOG.info("Couldn't service the LDAP connection", e);
+ }
+ return null;
+ }
+
+ /**
+ * Logs an incoming LDAP connection
+ *
+ * @param username
+ * the requesting user
+ */
+ protected void logIncomingConnection(final String username) {
+ LOG.info("AAA LDAP connection from {}", username);
+ Accounter.output("AAA LDAP connection from " + username);
+ }
+
+ /**
+ * Extracts the username from <code>token</code>
+ *
+ * @param token Encoded token which could contain a username
+ * @return The extracted username
+ * @throws ClassCastException
+ * The incoming token is not username/password (i.e., X.509
+ * certificate)
+ */
+ public static String getUsername(AuthenticationToken token) throws ClassCastException {
+ if (null == token) {
+ return null;
+ }
+ return (String) token.getPrincipal();
+ }
+
+ @Override
+ protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
+
+ AuthorizationInfo ai = null;
+ try {
+ ai = this.queryForAuthorizationInfo(principals, getContextFactory());
+ } catch (NamingException e) {
+ LOG.error("Unable to query for AuthZ info", e);
+ }
+ return ai;
+ }
+
+ /**
+ * extracts a username from <code>principals</code>
+ *
+ * @param principals A single principal extracted for the username
+ * @return The username if possible
+ * @throws ClassCastException
+ * the PrincipalCollection contains an element that is not in
+ * username/password form (i.e., X.509 certificate)
+ */
+ protected String getUsername(final PrincipalCollection principals) throws ClassCastException {
+
+ if (null == principals) {
+ return null;
+ }
+ return (String) getAvailablePrincipal(principals);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * This method is only called if doGetAuthenticationInfo(...) completes successfully AND
+ * the requested endpoint has an RBAC restriction. To add an RBAC restriction, edit the
+ * etc/shiro.ini file and add a url to the url section. E.g.,
+ *
+ * <code>/** = authcBasic, roles[person]</code>
+ *
+ * @see org.apache.shiro.realm.ldap.JndiLdapRealm#queryForAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection, org.apache.shiro.realm.ldap.LdapContextFactory)
+ */
+ @Override
+ protected AuthorizationInfo queryForAuthorizationInfo(PrincipalCollection principals,
+ LdapContextFactory ldapContextFactory) throws NamingException {
+
+ AuthorizationInfo authorizationInfo = null;
+ try {
+ final String username = getUsername(principals);
+ final LdapContext ldapContext = ldapContextFactory.getSystemLdapContext();
+ final Set<String> roleNames;
+
+ try {
+ roleNames = getRoleNamesForUser(username, ldapContext);
+ authorizationInfo = buildAuthorizationInfo(roleNames);
+ } finally {
+ LdapUtils.closeContext(ldapContext);
+ }
+ } catch (ClassCastException e) {
+ LOG.error("Unable to extract a valid user", e);
+ }
+ return authorizationInfo;
+ }
+
+ public static AuthorizationInfo buildAuthorizationInfo(final Set<String> roleNames) {
+ if (null == roleNames) {
+ return null;
+ }
+ return new SimpleAuthorizationInfo(roleNames);
+ }
+
+ /**
+ * extracts the Set of roles associated with a user based on the username
+ * and ldap context (server).
+ *
+ * @param username The username for the request
+ * @param ldapContext The specific system context provided by <code>shiro.ini</code>
+ * @return A set of roles
+ * @throws NamingException If the ldap search fails
+ */
+ protected Set<String> getRoleNamesForUser(final String username, final LdapContext ldapContext)
+ throws NamingException {
+
+ // Stores the role names, which are equivalent to the set of group names extracted
+ // from the LDAP query.
+ final Set<String> roleNames = new LinkedHashSet<String>();
+
+ final SearchControls searchControls = createSearchControls();
+
+ LOG.debug("Asking the configured LDAP about which groups uid=\"{}\" belongs to using "
+ + "searchBase=\"{}\" ldapAttributeForComparison=\"{}\"",
+ username, searchBase, ldapAttributeForComparison);
+ final NamingEnumeration<SearchResult> answer = ldapContext.search(searchBase,
+ String.format("%s=%s", UID, username), searchControls);
+
+ // Cursor based traversal over the LDAP query result
+ while (answer.hasMoreElements()) {
+ final SearchResult searchResult = answer.next();
+ final Attributes attrs = searchResult.getAttributes();
+ if (attrs != null) {
+ // Extract the attributes from the LDAP search.
+ // attrs.getAttr(String) was not chosen, since all attributes should be exposed
+ // in trace logging should the operator wish to use an alternate attribute.
+ final NamingEnumeration<? extends Attribute> ae = attrs.getAll();
+ while (ae.hasMore()) {
+ final Attribute attr = ae.next();
+ LOG.trace("LDAP returned \"{}\" attribute for \"{}\"", attr.getID(), username);
+ if (attr.getID().equals(ldapAttributeForComparison)) {
+ // Stresses the point that LDAP groups are EQUIVALENT to ODL role names
+ // TODO make this configurable via a Strategy pattern so more interesting mappings can be made
+ final Collection<String> groupNamesExtractedFromLdap = LdapUtils.getAllAttributeValues(attr);
+ final Collection<String> roleNamesFromLdapGroups = groupNamesExtractedFromLdap;
+ if (LOG.isTraceEnabled()) {
+ for (String roleName : roleNamesFromLdapGroups) {
+ LOG.trace("Mapped the \"{}\" LDAP group to ODL role for \"{}\"", roleName, username);
+ }
+ }
+ roleNames.addAll(roleNamesFromLdapGroups);
+ }
+ }
+ }
+ }
+ return roleNames;
+ }
+
+ /**
+ * A utility method to help create the search controls for the LDAP lookup
+ *
+ * @return A generic set of search controls for LDAP scoped to subtree
+ */
+ protected static SearchControls createSearchControls() {
+ SearchControls searchControls = new SearchControls();
+ searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
+ return searchControls;
+ }
+
+ @Override
+ public String getUserDnSuffix() {
+ return super.getUserDnSuffix();
+ }
+
+ /**
+ * Injected from <code>shiro.ini</code> configuration.
+ *
+ * @param searchBase The desired value for searchBase
+ */
+ public void setSearchBase(final String searchBase) {
+ // public for injection reasons
+ this.searchBase = searchBase;
+ }
+
+ /**
+ * Injected from <code>shiro.ini</code> configuration.
+ *
+ * @param ldapAttributeForComparison The attribute from which groups are extracted
+ */
+ public void setLdapAttributeForComparison(final String ldapAttributeForComparison) {
+ // public for injection reasons
+ this.ldapAttributeForComparison = ldapAttributeForComparison;
+ }
+}
diff --git a/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/realm/ODLJndiLdapRealmAuthNOnly.java b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/realm/ODLJndiLdapRealmAuthNOnly.java
new file mode 100644
index 00000000..978266c5
--- /dev/null
+++ b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/realm/ODLJndiLdapRealmAuthNOnly.java
@@ -0,0 +1,102 @@
+/*
+ * 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.realm;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.realm.ldap.JndiLdapRealm;
+import org.opendaylight.aaa.shiro.accounting.Accounter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Wrapper class for <code>org.apache.shiro.realm.ldap.JndiLdapRealm</code>.
+ * This implementation disables Authorization so any LDAP user is able to access
+ * server resources. This is particularly useful for quickly prototyping ODL
+ * without worrying about resolving LDAP attributes (groups) to OpenDaylight
+ * roles.
+ *
+ * The motivation for subclassing Shiro's implementation is two-fold: 1) Enhance
+ * the default logging of Shiro. This allows us to more easily log incoming
+ * connections, providing some security auditing. 2) Provide a common package in
+ * the classpath for ODL supported Realm implementations (i.e.,
+ * <code>org.opendaylight.aaa.shiro.realm</code>), which consolidates the number
+ * of <code>Import-Package</code> statements consumers need to enumerate. For
+ * example, the netconf project only needs to import
+ * <code>org.opendaylight.aaa.shiro.realm</code>, and does not need to worry
+ * about importing Shiro packages.
+ *
+ * @author Ryan Goulding (ryandgoulding@gmail.com)
+ *
+ */
+public class ODLJndiLdapRealmAuthNOnly extends JndiLdapRealm {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ODLJndiLdapRealmAuthNOnly.class);
+
+ private static final String LDAP_CONNECTION_MESSAGE = "AAA LDAP connection from ";
+
+ /*
+ * Adds debugging information surrounding creation of ODLJndiLdapRealm
+ */
+ public ODLJndiLdapRealmAuthNOnly() {
+ super();
+ LOG.debug("Creating ODLJndiLdapRealmAuthNOnly");
+ }
+
+ /*
+ * (non-Javadoc) Overridden to expose important audit trail information for
+ * accounting.
+ *
+ * @see
+ * org.apache.shiro.realm.ldap.JndiLdapRealm#doGetAuthenticationInfo(org
+ * .apache.shiro.authc.AuthenticationToken)
+ */
+ @Override
+ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
+ throws AuthenticationException {
+
+ try {
+ final String username = getUsername(token);
+ logIncomingConnection(username);
+ return super.doGetAuthenticationInfo(token);
+ } catch (ClassCastException e) {
+ LOG.info("Couldn't service the LDAP connection", e);
+ }
+ return null;
+ }
+
+ /**
+ * Logs an incoming LDAP connection
+ *
+ * @param username
+ * the requesting user
+ */
+ protected void logIncomingConnection(final String username) {
+ final String message = LDAP_CONNECTION_MESSAGE + username;
+ LOG.info(message);
+ Accounter.output(message);
+ }
+
+ /**
+ * Extracts the username from <code>token</code>
+ *
+ * @param token Which possibly contains a username
+ * @return the username if it can be extracted
+ * @throws ClassCastException
+ * The incoming token is not username/password (i.e., X.509
+ * certificate)
+ */
+ public static String getUsername(AuthenticationToken token) throws ClassCastException {
+ if (null == token) {
+ return null;
+ }
+ return (String) token.getPrincipal();
+ }
+}
diff --git a/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/realm/RadiusRealm.java b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/realm/RadiusRealm.java
new file mode 100644
index 00000000..51d4bfbf
--- /dev/null
+++ b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/realm/RadiusRealm.java
@@ -0,0 +1,37 @@
+/*
+ * 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.realm;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.realm.AuthorizingRealm;
+import org.apache.shiro.subject.PrincipalCollection;
+
+/**
+ * Implementation of a Radius AuthorizingRealm.
+ *
+ * @author Ryan Goulding (ryandgoulding@gmail.com)
+ */
+public class RadiusRealm extends AuthorizingRealm {
+
+ @Override
+ protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
+ // TODO use JRadius to extract Authorization Info
+ return null;
+ }
+
+ @Override
+ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0)
+ throws AuthenticationException {
+ // TODO use JRadius to extract Authentication Info
+ return null;
+ }
+
+}
diff --git a/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/realm/TACACSRealm.java b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/realm/TACACSRealm.java
new file mode 100644
index 00000000..38d7d91a
--- /dev/null
+++ b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/realm/TACACSRealm.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.realm;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.realm.AuthorizingRealm;
+import org.apache.shiro.subject.PrincipalCollection;
+
+/**
+ *
+ * @author Ryan Goulding (ryandgoulding@gmail.com)
+ *
+ */
+public class TACACSRealm extends AuthorizingRealm {
+
+ @Override
+ protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
+ // TODO Extract AuthorizationInfo using JNetLib
+ return null;
+ }
+
+ @Override
+ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0)
+ throws AuthenticationException {
+ // TODO Extract AuthenticationInfo using JNetLib
+ return null;
+ }
+
+}
diff --git a/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/realm/TokenAuthRealm.java b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/realm/TokenAuthRealm.java
new file mode 100644
index 00000000..f9ae5051
--- /dev/null
+++ b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/realm/TokenAuthRealm.java
@@ -0,0 +1,369 @@
+/*
+ * 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.realm;
+
+import com.google.common.base.Strings;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.SimpleAuthenticationInfo;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.authz.SimpleAuthorizationInfo;
+import org.apache.shiro.codec.Base64;
+import org.apache.shiro.realm.AuthorizingRealm;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.opendaylight.aaa.api.Authentication;
+import org.opendaylight.aaa.api.TokenAuth;
+import org.opendaylight.aaa.basic.HttpBasicAuth;
+import org.opendaylight.aaa.sts.ServiceLocator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * TokenAuthRealm is an adapter between the AAA shiro subsystem and the existing
+ * <code>TokenAuth</code> mechanisms. Thus, one can enable use of
+ * <code>IDMStore</code> and <code>IDMMDSALStore</code>.
+ *
+ * @author Ryan Goulding (ryandgoulding@gmail.com)
+ */
+public class TokenAuthRealm extends AuthorizingRealm {
+
+ private static final String USERNAME_DOMAIN_SEPARATOR = "@";
+
+ /**
+ * The unique identifying name for <code>TokenAuthRealm</code>
+ */
+ private static final String TOKEN_AUTH_REALM_DEFAULT_NAME = "TokenAuthRealm";
+
+ /**
+ * The message that is displayed if no <code>TokenAuth</code> interface is
+ * available yet
+ */
+ private static final String AUTHENTICATION_SERVICE_UNAVAILABLE_MESSAGE = "{\"error\":\"Authentication service unavailable\"}";
+
+ /**
+ * The message that is displayed if credentials are missing or malformed
+ */
+ private static final String FATAL_ERROR_DECODING_CREDENTIALS = "{\"error\":\"Unable to decode credentials\"}";
+
+ /**
+ * The message that is displayed if non-Basic Auth is attempted
+ */
+ private static final String FATAL_ERROR_BASIC_AUTH_ONLY = "{\"error\":\"Only basic authentication is supported by TokenAuthRealm\"}";
+
+ /**
+ * The purposefully generic message displayed if <code>TokenAuth</code> is
+ * unable to validate the given credentials
+ */
+ private static final String UNABLE_TO_AUTHENTICATE = "{\"error\":\"Could not authenticate\"}";
+
+ private static final Logger LOG = LoggerFactory.getLogger(TokenAuthRealm.class);
+
+ public TokenAuthRealm() {
+ super();
+ super.setName(TOKEN_AUTH_REALM_DEFAULT_NAME);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * Roles are derived from <code>TokenAuth.authenticate()</code>. Shiro roles
+ * are identical to existing IDM roles.
+ *
+ * @see
+ * org.apache.shiro.realm.AuthorizingRealm#doGetAuthorizationInfo(org.apache
+ * .shiro.subject.PrincipalCollection)
+ */
+ @Override
+ protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
+ final Object primaryPrincipal = principalCollection.getPrimaryPrincipal();
+ final ODLPrincipal odlPrincipal;
+ try {
+ odlPrincipal = (ODLPrincipal) primaryPrincipal;
+ return new SimpleAuthorizationInfo(odlPrincipal.getRoles());
+ } catch(ClassCastException e) {
+ LOG.error("Couldn't decode authorization request", e);
+ }
+ return new SimpleAuthorizationInfo();
+ }
+
+ /**
+ * Bridge new to old style <code>TokenAuth</code> interface.
+ *
+ * @param username The request username
+ * @param password The request password
+ * @param domain The request domain
+ * @return <code>username:password:domain</code>
+ */
+ static String getUsernamePasswordDomainString(final String username, final String password,
+ final String domain) {
+ return username + HttpBasicAuth.AUTH_SEP + password + HttpBasicAuth.AUTH_SEP + domain;
+ }
+
+ /**
+ *
+ * @param credentialToken
+ * @return Base64 encoded token
+ */
+ static String getEncodedToken(final String credentialToken) {
+ return Base64.encodeToString(credentialToken.getBytes());
+ }
+
+ /**
+ *
+ * @param encodedToken
+ * @return Basic <code>encodedToken</code>
+ */
+ static String getTokenAuthHeader(final String encodedToken) {
+ return HttpBasicAuth.BASIC_PREFIX + encodedToken;
+ }
+
+ /**
+ *
+ * @param tokenAuthHeader
+ * @return a map with the basic auth header
+ */
+ Map<String, List<String>> formHeadersWithToken(final String tokenAuthHeader) {
+ final Map<String, List<String>> headers = new HashMap<String, List<String>>();
+ final List<String> headerValue = new ArrayList<String>();
+ headerValue.add(tokenAuthHeader);
+ headers.put(HttpBasicAuth.AUTH_HEADER, headerValue);
+ return headers;
+ }
+
+ /**
+ * Adapter between basic authentication mechanism and existing
+ * <code>TokenAuth</code> interface.
+ *
+ * @param username Username from the request
+ * @param password Password from the request
+ * @param domain Domain from the request
+ * @return input map for <code>TokenAuth.validate()</code>
+ */
+ Map<String, List<String>> formHeaders(final String username, final String password,
+ final String domain) {
+ String usernamePasswordToken = getUsernamePasswordDomainString(username, password, domain);
+ String encodedToken = getEncodedToken(usernamePasswordToken);
+ String tokenAuthHeader = getTokenAuthHeader(encodedToken);
+ return formHeadersWithToken(tokenAuthHeader);
+ }
+
+ /**
+ * Adapter to check for available <code>TokenAuth<code> implementations.
+ *
+ * @return
+ */
+ boolean isTokenAuthAvailable() {
+ return ServiceLocator.getInstance().getAuthenticationService() != null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * Authenticates against any <code>TokenAuth</code> registered with the
+ * <code>ServiceLocator</code>
+ *
+ * @see
+ * org.apache.shiro.realm.AuthenticatingRealm#doGetAuthenticationInfo(org
+ * .apache.shiro.authc.AuthenticationToken)
+ */
+ @Override
+ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
+ throws AuthenticationException {
+
+ String username = "";
+ String password = "";
+ String domain = HttpBasicAuth.DEFAULT_DOMAIN;
+
+ try {
+ final String qualifiedUser = extractUsername(authenticationToken);
+ if (qualifiedUser.contains(USERNAME_DOMAIN_SEPARATOR)) {
+ final String [] qualifiedUserArray = qualifiedUser.split(USERNAME_DOMAIN_SEPARATOR);
+ try {
+ username = qualifiedUserArray[0];
+ domain = qualifiedUserArray[1];
+ } catch (ArrayIndexOutOfBoundsException e) {
+ LOG.trace("Couldn't parse domain from {}; trying without one",
+ qualifiedUser, e);
+ }
+ } else {
+ username = qualifiedUser;
+ }
+ password = extractPassword(authenticationToken);
+
+ } catch (NullPointerException e) {
+ throw new AuthenticationException(FATAL_ERROR_DECODING_CREDENTIALS, e);
+ } catch (ClassCastException e) {
+ throw new AuthenticationException(FATAL_ERROR_BASIC_AUTH_ONLY, e);
+ }
+
+ // check to see if there are TokenAuth implementations available
+ if (!isTokenAuthAvailable()) {
+ throw new AuthenticationException(AUTHENTICATION_SERVICE_UNAVAILABLE_MESSAGE);
+ }
+
+ // if the password is empty, this is an OAuth2 request, not a Basic HTTP
+ // Auth request
+ if (!Strings.isNullOrEmpty(password)) {
+ if (ServiceLocator.getInstance().getAuthenticationService().isAuthEnabled()) {
+ Map<String, List<String>> headers = formHeaders(username, password, domain);
+ // iterate over <code>TokenAuth</code> implementations and
+ // attempt to
+ // authentication with each one
+ final List<TokenAuth> tokenAuthCollection = ServiceLocator.getInstance()
+ .getTokenAuthCollection();
+ for (TokenAuth ta : tokenAuthCollection) {
+ try {
+ LOG.debug("Authentication attempt using {}", ta.getClass().getName());
+ final Authentication auth = ta.validate(headers);
+ if (auth != null) {
+ LOG.debug("Authentication attempt successful");
+ ServiceLocator.getInstance().getAuthenticationService().set(auth);
+ final ODLPrincipal odlPrincipal = ODLPrincipal.createODLPrincipal(auth);
+ return new SimpleAuthenticationInfo(odlPrincipal, password.toCharArray(),
+ getName());
+ }
+ } catch (AuthenticationException ae) {
+ LOG.debug("Authentication attempt unsuccessful");
+ throw new AuthenticationException(UNABLE_TO_AUTHENTICATE, ae);
+ }
+ }
+ }
+ }
+
+ // extract the authentication token and attempt validation of the token
+ final String token = extractUsername(authenticationToken);
+ final Authentication auth;
+ try {
+ auth = validate(token);
+ if (auth != null) {
+ final ODLPrincipal odlPrincipal = ODLPrincipal.createODLPrincipal(auth);
+ return new SimpleAuthenticationInfo(odlPrincipal, "", getName());
+ }
+ } catch (AuthenticationException e) {
+ LOG.debug("Unknown OAuth2 Token Access Request", e);
+ }
+
+ LOG.debug("Authentication failed: exhausted TokenAuth resources");
+ return null;
+ }
+
+ private Authentication validate(final String token) {
+ Authentication auth = ServiceLocator.getInstance().getTokenStore().get(token);
+ if (auth == null) {
+ throw new AuthenticationException("Could not validate the token " + token);
+ } else {
+ ServiceLocator.getInstance().getAuthenticationService().set(auth);
+ }
+ return auth;
+ }
+
+ /**
+ * extract the username from an <code>AuthenticationToken</code>
+ *
+ * @param authenticationToken
+ * @return
+ * @throws ClassCastException
+ * @throws NullPointerException
+ */
+ static String extractUsername(final AuthenticationToken authenticationToken)
+ throws ClassCastException, NullPointerException {
+
+ return (String) authenticationToken.getPrincipal();
+ }
+
+ /**
+ * extract the password from an <code>AuthenticationToken</code>
+ *
+ * @param authenticationToken
+ * @return
+ * @throws ClassCastException
+ * @throws NullPointerException
+ */
+ static String extractPassword(final AuthenticationToken authenticationToken)
+ throws ClassCastException, NullPointerException {
+
+ final UsernamePasswordToken upt = (UsernamePasswordToken) authenticationToken;
+ return new String(upt.getPassword());
+ }
+
+ /**
+ * Since <code>TokenAuthRealm</code> is an <code>AuthorizingRealm</code>, it supports
+ * individual steps for authentication and authorization. In ODL's existing <code>TokenAuth</code>
+ * mechanism, authentication and authorization are currently done in a single monolithic step.
+ * <code>ODLPrincipal</code> is abstracted as a DTO between the two steps. It fulfills the
+ * responsibility of a <code>Principal</code>, since it contains identification information
+ * but no credential information.
+ *
+ * @author Ryan Goulding (ryandgoulding@gmail.com)
+ */
+ private static class ODLPrincipal {
+
+ private final String username;
+ private final String domain;
+ private final String userId;
+ private final Set<String> roles;
+
+ private ODLPrincipal(final String username, final String domain, final String userId, final Set<String> roles) {
+ this.username = username;
+ this.domain = domain;
+ this.userId = userId;
+ this.roles = roles;
+ }
+
+ /**
+ * A static factory method to create <code>ODLPrincipal</code> instances.
+ *
+ * @param username The authenticated user
+ * @param domain The domain <code>username</code> belongs to.
+ * @param userId The unique key for <code>username</code>
+ * @param roles The roles associated with <code>username</code>@<code>domain</code>
+ * @return A Principal for the given session; essentially a DTO.
+ */
+ static ODLPrincipal createODLPrincipal(final String username, final String domain,
+ final String userId, final Set<String> roles) {
+
+ return new ODLPrincipal(username, domain, userId, roles);
+ }
+
+ /**
+ * A static factory method to create <code>ODLPrincipal</code> instances.
+ *
+ * @param auth Contains identifying information for the particular request.
+ * @return A Principal for the given session; essentially a DTO.
+ */
+ static ODLPrincipal createODLPrincipal(final Authentication auth) {
+ return createODLPrincipal(auth.user(), auth.domain(), auth.userId(), auth.roles());
+ }
+
+ String getUsername() {
+ return this.username;
+ }
+
+ String getDomain() {
+ return this.domain;
+ }
+
+ String getUserId() {
+ return this.userId;
+ }
+
+ Set<String> getRoles() {
+ return this.roles;
+ }
+ }
+}
diff --git a/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/web/env/KarafIniWebEnvironment.java b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/web/env/KarafIniWebEnvironment.java
new file mode 100644
index 00000000..acf4022c
--- /dev/null
+++ b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/web/env/KarafIniWebEnvironment.java
@@ -0,0 +1,125 @@
+/*
+ * 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.web.env;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.Collection;
+import org.apache.shiro.config.Ini;
+import org.apache.shiro.config.Ini.Section;
+import org.apache.shiro.web.env.IniWebEnvironment;
+import org.opendaylight.aaa.shiro.accounting.Accounter;
+import org.opendaylight.aaa.shiro.authorization.DefaultRBACRules;
+import org.opendaylight.aaa.shiro.authorization.RBACRule;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Identical to <code>IniWebEnvironment</code> except the Ini is loaded from
+ * <code>$KARAF_HOME/etc/shiro.ini</code>.
+ *
+ * @author Ryan Goulding (ryandgoulding@gmail.com)
+ *
+ */
+public class KarafIniWebEnvironment extends IniWebEnvironment {
+
+ private static final Logger LOG = LoggerFactory.getLogger(KarafIniWebEnvironment.class);
+ public static final String DEFAULT_SHIRO_INI_FILE = "etc/shiro.ini";
+ public static final String SHIRO_FILE_PREFIX = "file:/";
+
+ public KarafIniWebEnvironment() {
+ }
+
+ @Override
+ public void init() {
+ // Initialize the Shiro environment from etc/shiro.ini then delegate to
+ // the parent class
+ Ini ini;
+ try {
+ ini = createDefaultShiroIni();
+ // appendCustomIniRules(ini);
+ setIni(ini);
+ } catch (FileNotFoundException e) {
+ final String ERROR_MESSAGE = "Could not find etc/shiro.ini";
+ LOG.error(ERROR_MESSAGE, e);
+ }
+ super.init();
+ }
+
+ /**
+ * A hook for installing custom default RBAC rules for security purposes.
+ *
+ * @param ini
+ */
+ private void appendCustomIniRules(final Ini ini) {
+ final String INSTALL_MESSAGE = "Installing the RBAC rule: %s";
+ Section urlSection = getOrCreateUrlSection(ini);
+ Collection<RBACRule> rbacRules = DefaultRBACRules.getInstance().getRBACRules();
+ for (RBACRule rbacRule : rbacRules) {
+ urlSection.put(rbacRule.getUrlPattern(), rbacRule.getRolesInShiroFormat());
+ Accounter.output(String.format(INSTALL_MESSAGE, rbacRule));
+ }
+ }
+
+ /**
+ * Extracts the url section of the Ini file, or creates one if it doesn't
+ * already exist
+ *
+ * @param ini
+ * @return
+ */
+ private Section getOrCreateUrlSection(final Ini ini) {
+ final String URL_SECTION_TITLE = "urls";
+ Section urlSection = ini.getSection(URL_SECTION_TITLE);
+ if (urlSection == null) {
+ LOG.debug("shiro.ini does not contain a [urls] section; creating one");
+ urlSection = ini.addSection(URL_SECTION_TITLE);
+ } else {
+ LOG.debug("shiro.ini contains a [urls] section; appending rules to existing");
+ }
+ return urlSection;
+ }
+
+ /**
+ *
+ * @return Ini associated with <code>$KARAF_HOME/etc/shiro.ini</code>
+ * @throws FileNotFoundException
+ */
+ static Ini createDefaultShiroIni() throws FileNotFoundException {
+ return createShiroIni(DEFAULT_SHIRO_INI_FILE);
+ }
+
+ /**
+ *
+ * @param path
+ * the file path, which is either absolute or relative to
+ * <code>$KARAF_HOME</code>
+ * @return Ini loaded from <code>path</code>
+ */
+ static Ini createShiroIni(final String path) throws FileNotFoundException {
+ File f = new File(path);
+ Ini ini = new Ini();
+ final String fileBasedIniPath = createFileBasedIniPath(f.getAbsolutePath());
+ ini.loadFromPath(fileBasedIniPath);
+ return ini;
+ }
+
+ /**
+ *
+ * @param path
+ * the file path, which is either absolute or relative to
+ * <code>$KARAF_HOME</code>
+ * @return <code>file:/$KARAF_HOME/etc/shiro.ini</code>
+ */
+ static String createFileBasedIniPath(final String path) {
+ String fileBasedIniPath = SHIRO_FILE_PREFIX + path;
+ LOG.debug(fileBasedIniPath);
+ return fileBasedIniPath;
+ }
+}
diff --git a/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/resources/WEB-INF/web.xml b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/resources/WEB-INF/web.xml
new file mode 100644
index 00000000..63288c23
--- /dev/null
+++ b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/resources/WEB-INF/web.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+ version="3.0">
+
+ <servlet>
+ <servlet-name>MOON</servlet-name>
+ <servlet-class>org.opendaylight.aaa.shiro.moon.MoonTokenEndpoint</servlet-class>
+ <load-on-startup>1</load-on-startup>
+ </servlet>
+
+ <servlet-mapping>
+ <servlet-name>MOON</servlet-name>
+ <url-pattern>/token</url-pattern>
+ </servlet-mapping>
+ <servlet-mapping>
+ <servlet-name>MOON</servlet-name>
+ <url-pattern>/revoke</url-pattern>
+ </servlet-mapping>
+ <servlet-mapping>
+ <servlet-name>MOON</servlet-name>
+ <url-pattern>/validate</url-pattern>
+ </servlet-mapping>
+ <servlet-mapping>
+ <servlet-name>MOON</servlet-name>
+ <url-pattern>/*</url-pattern>
+ </servlet-mapping>
+
+ <!-- Shiro Filter -->
+ <context-param>
+ <param-name>shiroEnvironmentClass</param-name>
+ <param-value>org.opendaylight.aaa.shiro.web.env.KarafIniWebEnvironment</param-value>
+ </context-param>
+
+ <listener>
+ <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
+ </listener>
+
+ <filter>
+ <filter-name>ShiroFilter</filter-name>
+ <filter-class>org.opendaylight.aaa.shiro.filters.AAAFilter</filter-class>
+ </filter>
+
+ <filter-mapping>
+ <filter-name>ShiroFilter</filter-name>
+ <url-pattern>/*</url-pattern>
+ </filter-mapping>
+</web-app> \ No newline at end of file
diff --git a/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/resources/shiro.ini b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/resources/shiro.ini
new file mode 100644
index 00000000..b48abe96
--- /dev/null
+++ b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/resources/shiro.ini
@@ -0,0 +1,106 @@
+#
+# 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
+#
+
+###############################################################################
+# shiro.ini #
+# #
+# Configuration of OpenDaylight's aaa-shiro feature. Provided Realm #
+# implementations include: #
+# - TokenAuthRealm (enabled by default) #
+# - ODLJndiLdapRealm (disabled by default) #
+# - ODLJndiLdapRealmAuthNOnly (disabled by default) #
+# Basic user configuration through shiro.ini is disabled for security #
+# purposes. #
+###############################################################################
+
+
+
+[main]
+###############################################################################
+# realms #
+# #
+# This section is dedicated to setting up realms for OpenDaylight. Realms #
+# are essentially different methods for providing AAA. ODL strives to provide#
+# highly-configurable AAA by providing pluggable infrastructure. By deafult, #
+# TokenAuthRealm is enabled out of the box (which bridges to the existing AAA #
+# mechanisms). More than one realm can be enabled, and the realms are #
+# tried Round-Robin until: #
+# 1) a realm successfully authenticates the incoming request #
+# 2) all realms are exhausted, and 401 is returned #
+###############################################################################
+
+# ODL provides a few LDAP implementations, which are disabled out of the box.
+# ODLJndiLdapRealm includes authorization functionality based on LDAP elements
+# extracted through and LDAP search. This requires a bit of knowledge about
+# how your LDAP system is setup. An example is provided below:
+#ldapRealm = org.opendaylight.aaa.shiro.realm.ODLJndiLdapRealm
+#ldapRealm.userDnTemplate = uid={0},ou=People,dc=DOMAIN,dc=TLD
+#ldapRealm.contextFactory.url = ldap://<URL>:389
+#ldapRealm.searchBase = dc=DOMAIN,dc=TLD
+#ldapRealm.ldapAttributeForComparison = objectClass
+
+# ODL also provides ODLJndiLdapRealmAuthNOnly. Essentially, this allows
+# access through AAAFilter to any user that can authenticate against the
+# provided LDAP server.
+#ldapRealm = org.opendaylight.aaa.shiro.realm.ODLJndiLdapRealmAuthNOnly
+#ldapRealm.userDnTemplate = uid={0},ou=People,dc=DOMAIN,dc=TLD
+#ldapRealm.contextFactory.url = ldap://<URL>:389
+
+# Bridge to existing h2/idmlight/mdsal authentication/authorization mechanisms.
+# This realm is enabled by default, and utilizes h2-store by default.
+#tokenAuthRealm = org.opendaylight.aaa.shiro.realm.TokenAuthRealm
+# Defining moon realm
+moonAuthRealm = org.opendaylight.aaa.shiro.realm.MoonRealm
+
+# The CSV list of enabled realms. In order to enable a realm, add it to the
+# list below:
+#securityManager.realms = $tokenAuthRealm
+# Configure the Shiro Security Manager to use Moon Realm
+securityManager.realms = $moonAuthRealm
+
+# adds a custom AuthenticationFilter to support OAuth2 for backwards
+# compatibility. To disable OAuth2 access, just comment out the next line
+# and authcBasic will default to BasicHttpAuthenticationFilter, a
+# Shiro-provided class.
+authcBasic = org.opendaylight.aaa.shiro.filters.ODLHttpAuthenticationFilter
+# OAuth2 Filer for moon token AuthN
+rest = org.opendaylight.aaa.shiro.filters.MoonOAuthFilter
+
+# add in AuthenticationListener, a Listener that records whether
+# authentication attempts are successful or unsuccessful. This audit
+# information is disabled by default, to avoid log flooding. To enable,
+# issue the following in karaf:
+# >log:set DEBUG org.opendaylight.aaa.shiro.filters.AuthenticationListener
+accountingListener = org.opendaylight.aaa.shiro.filters.AuthenticationListener
+securityManager.authenticator.authenticationListeners = $accountingListener
+
+
+
+[urls]
+###############################################################################
+# url authorization section #
+# #
+# This section is dedicated to defining url-based authorization according to: #
+# http://shiro.apache.org/web.html #
+###############################################################################
+
+# Restrict AAA endpoints to users w/ admin role
+/v1/users/** = authcBasic
+/v1/domains/** = authcBasic
+/v1/roles/** = authcBasic
+
+#Filter OAuth2 request$
+/token = rest
+
+# General access through AAAFilter requires valid credentials (AuthN only).
+/** = authcBasic
+
+# Access to the credential store is limited to the valid users who have the
+# admin role. The following line is only needed if the mdsal store is enabled
+#(the mdsal store is disabled by default).
+/config/aaa-authn-model** = authcBasic,roles[admin]
diff --git a/upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/ServiceProxyTest.java b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/ServiceProxyTest.java
new file mode 100644
index 00000000..2d9c8976
--- /dev/null
+++ b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/ServiceProxyTest.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 static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.opendaylight.aaa.shiro.filters.AAAFilter;
+
+/**
+ * @author Ryan Goulding (ryandgoulding@gmail.com)
+ */
+public class ServiceProxyTest {
+
+ @Test
+ public void testGetInstance() {
+ // ensures that singleton pattern is working
+ assertNotNull(ServiceProxy.getInstance());
+ }
+
+ @Test
+ public void testGetSetEnabled() {
+ // combines set and get tests. These are important in this instance,
+ // because getEnabled allows an optional callback Filter.
+ ServiceProxy.getInstance().setEnabled(true);
+ assertTrue(ServiceProxy.getInstance().getEnabled(null));
+
+ AAAFilter testFilter = new AAAFilter();
+ // register the filter
+ ServiceProxy.getInstance().getEnabled(testFilter);
+ assertTrue(testFilter.isEnabled());
+
+ ServiceProxy.getInstance().setEnabled(false);
+ assertFalse(ServiceProxy.getInstance().getEnabled(testFilter));
+ assertFalse(testFilter.isEnabled());
+ }
+}
diff --git a/upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/TestAppender.java b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/TestAppender.java
new file mode 100644
index 00000000..ec9375dc
--- /dev/null
+++ b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/TestAppender.java
@@ -0,0 +1,67 @@
+/*
+ * 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 ch.qos.logback.classic.spi.LoggingEvent;
+import ch.qos.logback.core.AppenderBase;
+
+import java.util.List;
+import java.util.Vector;
+
+/**
+ * A custom slf4j <code>Appender</code> which stores <code>LoggingEvent</code>(s) in memory
+ * for future retrieval. This is useful from inside test resources. This class is specified
+ * within <code>logback-test.xml</code>.
+ *
+ * @author Ryan Goulding (ryandgoulding@gmail.com)
+ */
+public class TestAppender extends AppenderBase<LoggingEvent> {
+
+ /**
+ * stores all log events in memory, instead of file
+ */
+ private List<LoggingEvent> events = new Vector<>();
+
+ /**
+ * Since junit maven & junit instantiate the logging appender (as provided
+ * by logback-test.xml), singleton is not possible. The next best thing is to track the
+ * current instance so it can be retrieved by Test instances.
+ */
+ private static volatile TestAppender currentInstance;
+
+ /**
+ * keeps track of the current instance
+ */
+ public TestAppender() {
+ currentInstance = this;
+ }
+
+ @Override
+ protected void append(final LoggingEvent e) {
+ events.add(e);
+ }
+
+ /**
+ * Extract the log.
+ *
+ * @return the in-memory representation of <code>LoggingEvent</code>(s)
+ */
+ public List<LoggingEvent> getEvents() {
+ return events;
+ }
+
+ /**
+ * A way to extract the appender from Test instances.
+ *
+ * @return <code>this</code>
+ */
+ public static TestAppender getCurrentInstance() {
+ return currentInstance;
+ }
+}
diff --git a/upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/authorization/DefaultRBACRulesTest.java b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/authorization/DefaultRBACRulesTest.java
new file mode 100644
index 00000000..38658f0c
--- /dev/null
+++ b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/authorization/DefaultRBACRulesTest.java
@@ -0,0 +1,43 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import com.google.common.collect.Sets;
+import java.util.Collection;
+import org.junit.Test;
+
+/**
+ * A few basic test cases for the DefualtRBACRules singleton container.
+ *
+ * @author Ryan Goulding (ryandgoulding@gmail.com)
+ *
+ */
+public class DefaultRBACRulesTest {
+
+ @Test
+ public void testGetInstance() {
+ assertNotNull(DefaultRBACRules.getInstance());
+ assertEquals(DefaultRBACRules.getInstance(), DefaultRBACRules.getInstance());
+ }
+
+ @Test
+ public void testGetRBACRules() {
+ Collection<RBACRule> rbacRules = DefaultRBACRules.getInstance().getRBACRules();
+ assertNotNull(rbacRules);
+
+ // check that a copy was returned
+ int originalSize = rbacRules.size();
+ rbacRules.add(RBACRule.createAuthorizationRule("fakeurl/*", Sets.newHashSet("admin")));
+ assertEquals(originalSize, DefaultRBACRules.getInstance().getRBACRules().size());
+ }
+
+}
diff --git a/upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/authorization/RBACRuleTest.java b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/authorization/RBACRuleTest.java
new file mode 100644
index 00000000..825fe626
--- /dev/null
+++ b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/authorization/RBACRuleTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.collect.Sets;
+import java.util.Collection;
+import java.util.HashSet;
+import org.junit.Test;
+
+public class RBACRuleTest {
+
+ private static final String BASIC_RBAC_RULE_URL_PATTERN = "/*";
+ private static final Collection<String> BASIC_RBAC_RULE_ROLES = Sets.newHashSet("admin");
+ private RBACRule basicRBACRule = RBACRule.createAuthorizationRule(BASIC_RBAC_RULE_URL_PATTERN,
+ BASIC_RBAC_RULE_ROLES);
+
+ private static final String COMPLEX_RBAC_RULE_URL_PATTERN = "/auth/v1/";
+ private static final Collection<String> COMPLEX_RBAC_RULE_ROLES = Sets.newHashSet("admin",
+ "user");
+ private RBACRule complexRBACRule = RBACRule.createAuthorizationRule(
+ COMPLEX_RBAC_RULE_URL_PATTERN, COMPLEX_RBAC_RULE_ROLES);
+
+ @Test
+ public void testCreateAuthorizationRule() {
+ // positive test cases
+ assertNotNull(RBACRule.createAuthorizationRule(BASIC_RBAC_RULE_URL_PATTERN,
+ BASIC_RBAC_RULE_ROLES));
+ assertNotNull(RBACRule.createAuthorizationRule(COMPLEX_RBAC_RULE_URL_PATTERN,
+ COMPLEX_RBAC_RULE_ROLES));
+
+ // negative test cases
+ // both null
+ assertNull(RBACRule.createAuthorizationRule(null, null));
+
+ // url pattern is null
+ assertNull(RBACRule.createAuthorizationRule(null, BASIC_RBAC_RULE_ROLES));
+ // url pattern is empty string
+ assertNull(RBACRule.createAuthorizationRule("", BASIC_RBAC_RULE_ROLES));
+
+ // roles is null
+ assertNull(RBACRule.createAuthorizationRule(BASIC_RBAC_RULE_URL_PATTERN, null));
+ // roles is empty collection
+ assertNull(RBACRule.createAuthorizationRule(COMPLEX_RBAC_RULE_URL_PATTERN,
+ new HashSet<String>()));
+ }
+
+ @Test
+ public void testGetUrlPattern() {
+ assertEquals(BASIC_RBAC_RULE_URL_PATTERN, basicRBACRule.getUrlPattern());
+ assertEquals(COMPLEX_RBAC_RULE_URL_PATTERN, complexRBACRule.getUrlPattern());
+ }
+
+ @Test
+ public void testGetRoles() {
+ assertTrue(BASIC_RBAC_RULE_ROLES.containsAll(basicRBACRule.getRoles()));
+ basicRBACRule.getRoles().clear();
+ // test that getRoles() produces a new object
+ assertFalse(basicRBACRule.getRoles().isEmpty());
+ assertTrue(basicRBACRule.getRoles().containsAll(BASIC_RBAC_RULE_ROLES));
+
+ assertTrue(COMPLEX_RBAC_RULE_ROLES.containsAll(complexRBACRule.getRoles()));
+ complexRBACRule.getRoles().add("newRole");
+ // test that getRoles() produces a new object
+ assertFalse(complexRBACRule.getRoles().contains("newRole"));
+ assertTrue(complexRBACRule.getRoles().containsAll(COMPLEX_RBAC_RULE_ROLES));
+ }
+
+ @Test
+ public void testGetRolesInShiroFormat() {
+ final String BASIC_RBAC_RULE_EXPECTED_SHIRO_FORMAT = "roles[admin]";
+ assertEquals(BASIC_RBAC_RULE_EXPECTED_SHIRO_FORMAT, basicRBACRule.getRolesInShiroFormat());
+
+ // set ordering is not predictable, so both formats must be considered
+ final String COMPLEX_RBAC_RULE_EXPECTED_SHIRO_FORMAT_1 = "roles[admin, user]";
+ final String COMPLEX_RBAC_RULE_EXPECTED_SHIRO_FORMAT_2 = "roles[user, admin]";
+ assertTrue(COMPLEX_RBAC_RULE_EXPECTED_SHIRO_FORMAT_1.equals(complexRBACRule
+ .getRolesInShiroFormat())
+ || COMPLEX_RBAC_RULE_EXPECTED_SHIRO_FORMAT_2.equals(complexRBACRule
+ .getRolesInShiroFormat()));
+ }
+
+ @Test
+ public void testToString() {
+ final String BASIC_RBAC_RULE_EXPECTED_SHIRO_FORMAT = "/*=roles[admin]";
+ assertEquals(BASIC_RBAC_RULE_EXPECTED_SHIRO_FORMAT, basicRBACRule.toString());
+
+ // set ordering is not predictable,s o both formats must be considered
+ final String COMPLEX_RBAC_RULE_EXPECTED_SHIRO_FORMAT_1 = "/auth/v1/=roles[admin, user]";
+ final String COMPLEX_RBAC_RULE_EXPECTED_SHIRO_FORMAT_2 = "/auth/v1/=roles[user, admin]";
+ assertTrue(COMPLEX_RBAC_RULE_EXPECTED_SHIRO_FORMAT_1.equals(complexRBACRule.toString())
+ || COMPLEX_RBAC_RULE_EXPECTED_SHIRO_FORMAT_2.equals(complexRBACRule.toString()));
+ }
+
+}
diff --git a/upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/filters/AuthenticationListenerTest.java b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/filters/AuthenticationListenerTest.java
new file mode 100644
index 00000000..1c823525
--- /dev/null
+++ b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/filters/AuthenticationListenerTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.filters;
+
+import static org.junit.Assert.*;
+
+import ch.qos.logback.classic.spi.LoggingEvent;
+
+import java.util.List;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.SimpleAuthenticationInfo;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.junit.Test;
+import org.opendaylight.aaa.shiro.TestAppender;
+import org.opendaylight.aaa.shiro.filters.AuthenticationListener;
+
+/**
+ * Test AuthenticationListener, which is responsible for logging Accounting events.
+ *
+ * @author Ryan Goulding (ryandgoulding@gmail.com)
+ */
+public class AuthenticationListenerTest {
+
+ @Test
+ public void testOnSuccess() throws Exception {
+ // sets up a successful authentication attempt
+ final AuthenticationListener authenticationListener = new AuthenticationListener();
+ final UsernamePasswordToken authenticationToken = new UsernamePasswordToken();
+ authenticationToken.setUsername("successfulUser1");
+ authenticationToken.setHost("successfulHost1");
+ final SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo();
+ // the following call produces accounting output
+ authenticationListener.onSuccess(authenticationToken, simpleAuthenticationInfo);
+
+ // grab the latest log output and make sure it is in line with what is expected
+ final List<LoggingEvent> loggingEvents = TestAppender.getCurrentInstance().getEvents();
+ // the latest logging event is the one we need to inspect
+ final int whichLoggingEvent = loggingEvents.size() - 1;
+ final LoggingEvent latestLoggingEvent = loggingEvents.get(whichLoggingEvent);
+ final String latestLogMessage = latestLoggingEvent.getMessage();
+ assertEquals("Successful authentication attempt by successfulUser1 from successfulHost1",
+ latestLogMessage);
+ }
+
+ @Test
+ public void testOnFailure() throws Exception {
+ // variables for an unsucessful authentication attempt
+ final AuthenticationListener authenticationListener = new AuthenticationListener();
+ final UsernamePasswordToken authenticationToken = new UsernamePasswordToken();
+ authenticationToken.setUsername("unsuccessfulUser1");
+ authenticationToken.setHost("unsuccessfulHost1");
+ final AuthenticationException authenticationException =
+ new AuthenticationException("test auth exception");
+ // produces unsuccessful authentication attempt output
+ authenticationListener.onFailure(authenticationToken, authenticationException);
+
+ // grab the latest log output and ensure it is in line with what is expected
+ final List<LoggingEvent> loggingEvents = TestAppender.getCurrentInstance().getEvents();
+ final int whichLoggingEvent = loggingEvents.size() - 1;
+ final LoggingEvent latestLoggingEvent = loggingEvents.get(whichLoggingEvent);
+ final String latestLogMessage = latestLoggingEvent.getMessage();
+ assertEquals("Unsuccessful authentication attempt by unsuccessfulUser1 from unsuccessfulHost1",
+ latestLogMessage);
+ }
+} \ No newline at end of file
diff --git a/upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/filters/AuthenticationTokenUtilsTest.java b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/filters/AuthenticationTokenUtilsTest.java
new file mode 100644
index 00000000..09331c52
--- /dev/null
+++ b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/filters/AuthenticationTokenUtilsTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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.filters;
+
+import static org.junit.Assert.*;
+
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.junit.Test;
+import org.opendaylight.aaa.shiro.filters.AuthenticationTokenUtils;
+
+/**
+ * Tests authentication token output utilities.
+ *
+ * @author Ryan Goulding (ryandgoulding@gmail.com)
+ */
+public class AuthenticationTokenUtilsTest {
+
+ /**
+ * A sample non-UsernamePasswordToken implementation for testing.
+ */
+ private final class NotUsernamePasswordToken implements AuthenticationToken {
+
+ @Override
+ public Object getPrincipal() {
+ return null;
+ }
+
+ @Override
+ public Object getCredentials() {
+ return null;
+ }
+ }
+
+ @Test
+ public void testIsUsernamePasswordToken() throws Exception {
+ // null test
+ final AuthenticationToken nullUsernamePasswordToken = null;
+ assertFalse(AuthenticationTokenUtils.isUsernamePasswordToken(nullUsernamePasswordToken));
+
+ // alternate implementation of AuthenticationToken
+ final AuthenticationToken notUsernamePasswordToken = new NotUsernamePasswordToken();
+ assertFalse(AuthenticationTokenUtils.isUsernamePasswordToken(notUsernamePasswordToken));
+
+ // positive test case
+ final AuthenticationToken positiveUsernamePasswordToken = new UsernamePasswordToken();
+ assertTrue(AuthenticationTokenUtils.isUsernamePasswordToken(positiveUsernamePasswordToken));
+
+ }
+
+ @Test
+ public void testExtractUsername() throws Exception {
+ // null test
+ final AuthenticationToken nullAuthenticationToken = null;
+ assertEquals(AuthenticationTokenUtils.DEFAULT_TOKEN,
+ AuthenticationTokenUtils.extractUsername(nullAuthenticationToken));
+
+ // non-UsernamePasswordToken test
+ final AuthenticationToken notUsernamePasswordToken = new NotUsernamePasswordToken();
+ assertEquals(AuthenticationTokenUtils.DEFAULT_TOKEN,
+ AuthenticationTokenUtils.extractUsername(notUsernamePasswordToken));
+
+ // null username test
+ final UsernamePasswordToken nullUsername = new UsernamePasswordToken();
+ nullUsername.setUsername(null);
+ assertEquals(AuthenticationTokenUtils.DEFAULT_USERNAME,
+ AuthenticationTokenUtils.extractUsername(nullUsername));
+
+ // positive test
+ final UsernamePasswordToken positiveUsernamePasswordToken = new UsernamePasswordToken();
+ final String testUsername = "testUser1";
+ positiveUsernamePasswordToken.setUsername(testUsername);
+ assertEquals(testUsername, AuthenticationTokenUtils.extractUsername(positiveUsernamePasswordToken));
+ }
+
+ @Test
+ public void testExtractHostname() throws Exception {
+ // null test
+ final AuthenticationToken nullAuthenticationToken = null;
+ assertEquals(AuthenticationTokenUtils.DEFAULT_HOSTNAME,
+ AuthenticationTokenUtils.extractHostname(nullAuthenticationToken));
+
+ // non-UsernamePasswordToken test
+ final AuthenticationToken notUsernamePasswordToken = new NotUsernamePasswordToken();
+ assertEquals(AuthenticationTokenUtils.DEFAULT_HOSTNAME,
+ AuthenticationTokenUtils.extractHostname(notUsernamePasswordToken));
+
+ // null hostname test
+ final UsernamePasswordToken nullHostname = new UsernamePasswordToken();
+ nullHostname.setHost(null);
+ assertEquals(AuthenticationTokenUtils.DEFAULT_HOSTNAME,
+ AuthenticationTokenUtils.extractHostname(nullHostname));
+
+ // positive test
+ final UsernamePasswordToken positiveUsernamePasswordToken = new UsernamePasswordToken();
+ final String testUsername = "testHostname1";
+ positiveUsernamePasswordToken.setHost(testUsername);
+ assertEquals(testUsername, AuthenticationTokenUtils.extractHostname(positiveUsernamePasswordToken));
+ }
+
+ @Test
+ public void testGenerateUnsuccessfulAuthenticationMessage() throws Exception {
+ final UsernamePasswordToken unsuccessfulToken = new UsernamePasswordToken();
+ unsuccessfulToken.setUsername("unsuccessfulUser1");
+ unsuccessfulToken.setHost("unsuccessfulHost1");
+ assertEquals("Unsuccessful authentication attempt by unsuccessfulUser1 from unsuccessfulHost1",
+ AuthenticationTokenUtils.generateUnsuccessfulAuthenticationMessage(unsuccessfulToken));
+ }
+
+ @Test
+ public void testGenerateSuccessfulAuthenticationMessage() throws Exception {
+ final UsernamePasswordToken successfulToken = new UsernamePasswordToken();
+ successfulToken.setUsername("successfulUser1");
+ successfulToken.setHost("successfulHost1");
+ assertEquals("Successful authentication attempt by successfulUser1 from successfulHost1",
+ AuthenticationTokenUtils.generateSuccessfulAuthenticationMessage(successfulToken));
+ }
+}
diff --git a/upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/realm/ODLJndiLdapRealmTest.java b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/realm/ODLJndiLdapRealmTest.java
new file mode 100644
index 00000000..22ce203f
--- /dev/null
+++ b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/realm/ODLJndiLdapRealmTest.java
@@ -0,0 +1,246 @@
+/*
+ * 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.realm;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.Vector;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import javax.naming.ldap.LdapContext;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.realm.ldap.LdapContextFactory;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.junit.Test;
+
+/**
+ * @author Ryan Goulding (ryandgoulding@gmail.com)
+ */
+public class ODLJndiLdapRealmTest {
+
+ /**
+ * throw-away anonymous test class
+ */
+ class TestNamingEnumeration implements NamingEnumeration<SearchResult> {
+
+ /**
+ * state variable
+ */
+ boolean first = true;
+
+ /**
+ * returned the first time <code>next()</code> or
+ * <code>nextElement()</code> is called.
+ */
+ SearchResult searchResult = new SearchResult("testuser", null, new BasicAttributes(
+ "objectClass", "engineering"));
+
+ /**
+ * returns true the first time, then false for subsequent calls
+ */
+ @Override
+ public boolean hasMoreElements() {
+ return first;
+ }
+
+ /**
+ * returns <code>searchResult</code> then null for subsequent calls
+ */
+ @Override
+ public SearchResult nextElement() {
+ if (first) {
+ first = false;
+ return searchResult;
+ }
+ return null;
+ }
+
+ /**
+ * does nothing because close() doesn't require any special behavior
+ */
+ @Override
+ public void close() throws NamingException {
+ }
+
+ /**
+ * returns true the first time, then false for subsequent calls
+ */
+ @Override
+ public boolean hasMore() throws NamingException {
+ return first;
+ }
+
+ /**
+ * returns <code>searchResult</code> then null for subsequent calls
+ */
+ @Override
+ public SearchResult next() throws NamingException {
+ if (first) {
+ first = false;
+ return searchResult;
+ }
+ return null;
+ }
+ };
+
+ /**
+ * throw away test class
+ *
+ * @author ryan
+ */
+ class TestPrincipalCollection implements PrincipalCollection {
+ /**
+ *
+ */
+ private static final long serialVersionUID = -1236759619455574475L;
+
+ Vector<String> collection = new Vector<String>();
+
+ public TestPrincipalCollection(String element) {
+ collection.add(element);
+ }
+
+ @Override
+ public Iterator<String> iterator() {
+ return collection.iterator();
+ }
+
+ @Override
+ public List<String> asList() {
+ return collection;
+ }
+
+ @Override
+ public Set<String> asSet() {
+ HashSet<String> set = new HashSet<String>();
+ set.addAll(collection);
+ return set;
+ }
+
+ @Override
+ public <T> Collection<T> byType(Class<T> arg0) {
+ return null;
+ }
+
+ @Override
+ public Collection<String> fromRealm(String arg0) {
+ return collection;
+ }
+
+ @Override
+ public Object getPrimaryPrincipal() {
+ return collection.firstElement();
+ }
+
+ @Override
+ public Set<String> getRealmNames() {
+ return null;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return collection.isEmpty();
+ }
+
+ @Override
+ public <T> T oneByType(Class<T> arg0) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+ };
+
+ @Test
+ public void testGetUsernameAuthenticationToken() {
+ AuthenticationToken authenticationToken = null;
+ assertNull(ODLJndiLdapRealm.getUsername(authenticationToken));
+ AuthenticationToken validAuthenticationToken = new UsernamePasswordToken("test",
+ "testpassword");
+ assertEquals("test", ODLJndiLdapRealm.getUsername(validAuthenticationToken));
+ }
+
+ @Test
+ public void testGetUsernamePrincipalCollection() {
+ PrincipalCollection pc = null;
+ assertNull(new ODLJndiLdapRealm().getUsername(pc));
+ TestPrincipalCollection tpc = new TestPrincipalCollection("testuser");
+ String username = new ODLJndiLdapRealm().getUsername(tpc);
+ assertEquals("testuser", username);
+ }
+
+ @Test
+ public void testQueryForAuthorizationInfoPrincipalCollectionLdapContextFactory()
+ throws NamingException {
+ LdapContext ldapContext = mock(LdapContext.class);
+ // emulates an ldap search and returns the mocked up test class
+ when(
+ ldapContext.search((String) any(), (String) any(),
+ (SearchControls) any())).thenReturn(new TestNamingEnumeration());
+ LdapContextFactory ldapContextFactory = mock(LdapContextFactory.class);
+ when(ldapContextFactory.getSystemLdapContext()).thenReturn(ldapContext);
+ AuthorizationInfo authorizationInfo = new ODLJndiLdapRealm().queryForAuthorizationInfo(
+ new TestPrincipalCollection("testuser"), ldapContextFactory);
+ assertNotNull(authorizationInfo);
+ assertFalse(authorizationInfo.getRoles().isEmpty());
+ assertTrue(authorizationInfo.getRoles().contains("engineering"));
+ }
+
+ @Test
+ public void testBuildAuthorizationInfo() {
+ assertNull(ODLJndiLdapRealm.buildAuthorizationInfo(null));
+ Set<String> roleNames = new HashSet<String>();
+ roleNames.add("engineering");
+ AuthorizationInfo authorizationInfo = ODLJndiLdapRealm.buildAuthorizationInfo(roleNames);
+ assertNotNull(authorizationInfo);
+ assertFalse(authorizationInfo.getRoles().isEmpty());
+ assertTrue(authorizationInfo.getRoles().contains("engineering"));
+ }
+
+ @Test
+ public void testGetRoleNamesForUser() throws NamingException {
+ ODLJndiLdapRealm ldapRealm = new ODLJndiLdapRealm();
+ LdapContext ldapContext = mock(LdapContext.class);
+
+ // emulates an ldap search and returns the mocked up test class
+ when(
+ ldapContext.search((String) any(), (String) any(),
+ (SearchControls) any())).thenReturn(new TestNamingEnumeration());
+
+ // extracts the roles for "testuser" and ensures engineering is returned
+ Set<String> roles = ldapRealm.getRoleNamesForUser("testuser", ldapContext);
+ assertFalse(roles.isEmpty());
+ assertTrue(roles.iterator().next().equals("engineering"));
+ }
+
+ @Test
+ public void testCreateSearchControls() {
+ SearchControls searchControls = ODLJndiLdapRealm.createSearchControls();
+ assertNotNull(searchControls);
+ int expectedSearchScope = SearchControls.SUBTREE_SCOPE;
+ int actualSearchScope = searchControls.getSearchScope();
+ assertEquals(expectedSearchScope, actualSearchScope);
+ }
+
+}
diff --git a/upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/realm/TokenAuthRealmTest.java b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/realm/TokenAuthRealmTest.java
new file mode 100644
index 00000000..f2eb92b5
--- /dev/null
+++ b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/realm/TokenAuthRealmTest.java
@@ -0,0 +1,139 @@
+/*
+ * 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.realm;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.Lists;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.junit.Test;
+
+/**
+ *
+ * @author Ryan Goulding (ryandgoulding@gmail.com)
+ *
+ */
+public class TokenAuthRealmTest extends TokenAuthRealm {
+
+ private TokenAuthRealm testRealm = new TokenAuthRealm();
+
+ @Test
+ public void testTokenAuthRealm() {
+ assertEquals("TokenAuthRealm", testRealm.getName());
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testDoGetAuthorizationInfoPrincipalCollectionNullCacheToken() {
+ testRealm.doGetAuthorizationInfo(null);
+ }
+
+ @Test
+ public void testGetUsernamePasswordDomainString() {
+ final String username = "user";
+ final String password = "password";
+ final String domain = "domain";
+ final String expectedUsernamePasswordString = "user:password:domain";
+ assertEquals(expectedUsernamePasswordString, getUsernamePasswordDomainString(username, password, domain));
+ }
+
+ @Test
+ public void testGetEncodedToken() {
+ final String stringToEncode = "admin1:admin1";
+ final byte[] bytesToEncode = stringToEncode.getBytes();
+ final String expectedToken = org.apache.shiro.codec.Base64.encodeToString(bytesToEncode);
+ assertEquals(expectedToken, getEncodedToken(stringToEncode));
+ }
+
+ @Test
+ public void testGetTokenAuthHeader() {
+ final String encodedCredentials = getEncodedToken(getUsernamePasswordDomainString("user1",
+ "password", "sdn"));
+ final String expectedTokenAuthHeader = "Basic " + encodedCredentials;
+ assertEquals(expectedTokenAuthHeader, getTokenAuthHeader(encodedCredentials));
+ }
+
+ @Test
+ public void testFormHeadersWithToken() {
+ final String authHeader = getEncodedToken(getTokenAuthHeader(getUsernamePasswordDomainString(
+ "user1", "password", "sdn")));
+ final Map<String, List<String>> expectedHeaders = new HashMap<String, List<String>>();
+ expectedHeaders.put("Authorization", Lists.newArrayList(authHeader));
+ final Map<String, List<String>> actualHeaders = formHeadersWithToken(authHeader);
+ List<String> value;
+ for (String key : expectedHeaders.keySet()) {
+ value = expectedHeaders.get(key);
+ assertTrue(actualHeaders.get(key).equals(value));
+ }
+ }
+
+ @Test
+ public void testFormHeaders() {
+ final String username = "basicUser";
+ final String password = "basicPassword";
+ final String domain = "basicDomain";
+ final String authHeader = getTokenAuthHeader(getEncodedToken(getUsernamePasswordDomainString(
+ username, password, domain)));
+ final Map<String, List<String>> expectedHeaders = new HashMap<String, List<String>>();
+ expectedHeaders.put("Authorization", Lists.newArrayList(authHeader));
+ final Map<String, List<String>> actualHeaders = formHeaders(username, password, domain);
+ List<String> value;
+ for (String key : expectedHeaders.keySet()) {
+ value = expectedHeaders.get(key);
+ assertTrue(actualHeaders.get(key).equals(value));
+ }
+ }
+
+ @Test
+ public void testIsTokenAuthAvailable() {
+ assertFalse(testRealm.isTokenAuthAvailable());
+ }
+
+ @Test(expected = org.apache.shiro.authc.AuthenticationException.class)
+ public void testDoGetAuthenticationInfoAuthenticationToken() {
+ testRealm.doGetAuthenticationInfo(null);
+ }
+
+ @Test
+ public void testExtractUsernameNullUsername() {
+ AuthenticationToken at = mock(AuthenticationToken.class);
+ when(at.getPrincipal()).thenReturn(null);
+ assertNull(extractUsername(at));
+ }
+
+ @Test(expected = ClassCastException.class)
+ public void testExtractPasswordNullPassword() {
+ AuthenticationToken at = mock(AuthenticationToken.class);
+ when(at.getPrincipal()).thenReturn("username");
+ when(at.getCredentials()).thenReturn(null);
+ extractPassword(at);
+ }
+
+ @Test(expected = ClassCastException.class)
+ public void testExtractUsernameBadUsernameClass() {
+ AuthenticationToken at = mock(AuthenticationToken.class);
+ when(at.getPrincipal()).thenReturn(new Integer(1));
+ extractUsername(at);
+ }
+
+ @Test(expected = ClassCastException.class)
+ public void testExtractPasswordBadPasswordClass() {
+ AuthenticationToken at = mock(AuthenticationToken.class);
+ when(at.getPrincipal()).thenReturn("username");
+ when(at.getCredentials()).thenReturn(new Integer(1));
+ extractPassword(at);
+ }
+}
diff --git a/upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/web/env/KarafIniWebEnvironmentTest.java b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/web/env/KarafIniWebEnvironmentTest.java
new file mode 100644
index 00000000..141d0ce5
--- /dev/null
+++ b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/java/org/opendaylight/aaa/shiro/web/env/KarafIniWebEnvironmentTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.web.env;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import org.apache.shiro.config.Ini;
+import org.apache.shiro.config.Ini.Section;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * @author Ryan Goulding (ryandgoulding@gmail.com)
+ */
+public class KarafIniWebEnvironmentTest {
+ private static File iniFile;
+
+ @BeforeClass
+ public static void setup() throws IOException {
+ iniFile = createShiroIniFile();
+ assertTrue(iniFile.exists());
+ }
+
+ @AfterClass
+ public static void teardown() {
+ iniFile.delete();
+ }
+
+ private static String createFakeShiroIniContents() {
+ return "[users]\n" + "admin=admin, ROLE_ADMIN \n" + "[roles]\n" + "ROLE_ADMIN = *\n"
+ + "[urls]\n" + "/** = authcBasic";
+ }
+
+ private static File createShiroIniFile() throws IOException {
+ File shiroIni = File.createTempFile("shiro", "ini");
+ FileWriter writer = new FileWriter(shiroIni);
+ writer.write(createFakeShiroIniContents());
+ writer.flush();
+ writer.close();
+ return shiroIni;
+ }
+
+ @Test
+ public void testCreateShiroIni() throws IOException {
+ Ini ini = KarafIniWebEnvironment.createShiroIni(iniFile.getAbsolutePath());
+ assertNotNull(ini);
+ assertNotNull(ini.getSection("users"));
+ assertNotNull(ini.getSection("roles"));
+ assertNotNull(ini.getSection("urls"));
+ Section usersSection = ini.getSection("users");
+ assertTrue(usersSection.containsKey("admin"));
+ assertTrue(usersSection.get("admin").contains("admin"));
+ assertTrue(usersSection.get("admin").contains("ROLE_ADMIN"));
+ }
+
+ @Test
+ public void testCreateFileBasedIniPath() {
+ String testPath = "/shiro.ini";
+ String expectedFileBasedIniPath = KarafIniWebEnvironment.SHIRO_FILE_PREFIX + testPath;
+ String actualFileBasedIniPath = KarafIniWebEnvironment.createFileBasedIniPath(testPath);
+ assertEquals(expectedFileBasedIniPath, actualFileBasedIniPath);
+ }
+
+}
diff --git a/upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/resources/logback-test.xml b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/resources/logback-test.xml
new file mode 100644
index 00000000..68ceeabc
--- /dev/null
+++ b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/test/resources/logback-test.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+
+ <appender name="TEST-APPENDER" class="org.opendaylight.aaa.shiro.TestAppender">
+ <layout class="ch.qos.logback.classic.PatternLayout">
+ <Pattern>
+ %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
+ </Pattern>
+ </layout>
+ </appender>
+
+ <logger name="org.opendaylight.aaa.shiro.authc" level="debug"
+ additivity="false">
+ <appender-ref ref="TEST-APPENDER" />
+ </logger>
+
+ <root level="debug">
+ <appender-ref ref="TEST-APPENDER" />
+ </root>
+
+</configuration>