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