/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.cxf.rs.security.oauth.filters; import java.security.Principal; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import net.oauth.OAuth; import net.oauth.OAuthMessage; import net.oauth.OAuthProblemException; import net.oauth.OAuthValidator; import net.oauth.server.OAuthServlet; import org.apache.cxf.common.logging.LogUtils; import org.apache.cxf.common.security.SimplePrincipal; import org.apache.cxf.configuration.security.AuthorizationPolicy; import org.apache.cxf.message.Message; import org.apache.cxf.phase.PhaseInterceptorChain; import org.apache.cxf.rs.security.oauth.data.AccessToken; import org.apache.cxf.rs.security.oauth.data.Client; import org.apache.cxf.rs.security.oauth.data.OAuthContext; import org.apache.cxf.rs.security.oauth.data.OAuthPermission; import org.apache.cxf.rs.security.oauth.data.UserSubject; import org.apache.cxf.rs.security.oauth.provider.DefaultOAuthValidator; import org.apache.cxf.rs.security.oauth.provider.OAuthDataProvider; import org.apache.cxf.rs.security.oauth.utils.OAuthConstants; import org.apache.cxf.rs.security.oauth.utils.OAuthUtils; import org.apache.cxf.security.SecurityContext; /** * Base OAuth filter which can be used to protect end-user endpoints */ public class AbstractAuthFilter { private static final Logger LOG = LogUtils.getL7dLogger(AbstractAuthFilter.class); private static final String[] REQUIRED_PARAMETERS = new String[] { OAuth.OAUTH_CONSUMER_KEY, OAuth.OAUTH_TOKEN, OAuth.OAUTH_SIGNATURE_METHOD, OAuth.OAUTH_SIGNATURE, OAuth.OAUTH_TIMESTAMP, OAuth.OAUTH_NONCE }; private static final Set<String> ALLOWED_OAUTH_PARAMETERS; static { ALLOWED_OAUTH_PARAMETERS = new HashSet<>(); ALLOWED_OAUTH_PARAMETERS.addAll(Arrays.asList(REQUIRED_PARAMETERS)); ALLOWED_OAUTH_PARAMETERS.add(OAuth.OAUTH_VERSION); ALLOWED_OAUTH_PARAMETERS.add(OAuthConstants.OAUTH_CONSUMER_SECRET); } private boolean supportUnknownParameters; private boolean useUserSubject; private OAuthDataProvider dataProvider; private OAuthValidator validator = new DefaultOAuthValidator(); protected AbstractAuthFilter() { } /** * Sets {@link OAuthDataProvider} provider. * @param provider the provider */ public void setDataProvider(OAuthDataProvider provider) { dataProvider = provider; } public void setUseUserSubject(boolean useUserSubject) { this.useUserSubject = useUserSubject; } public boolean isUseUserSubject() { return useUserSubject; } /** * Authenticates the third-party consumer and returns * {@link OAuthInfo} bean capturing the information about the request. * @param req http request * @return OAuth info * @see OAuthInfo * @throws Exception * @throws OAuthProblemException */ protected OAuthInfo handleOAuthRequest(HttpServletRequest req) throws Exception, OAuthProblemException { if (LOG.isLoggable(Level.FINE)) { LOG.log(Level.FINE, "OAuth security filter for url: {0}", req.getRequestURL()); } AccessToken accessToken = null; Client client = null; OAuthMessage oAuthMessage = OAuthServlet.getMessage(new CustomHttpServletWrapper(req), OAuthServlet.getRequestURL(req)); if (oAuthMessage.getParameter(OAuth.OAUTH_TOKEN) != null) { oAuthMessage.requireParameters(REQUIRED_PARAMETERS); accessToken = dataProvider.getAccessToken(oAuthMessage.getToken()); //check if access token is not null if (accessToken == null) { LOG.warning("Access token is unavailable"); throw new OAuthProblemException(OAuth.Problems.TOKEN_REJECTED); } client = accessToken.getClient(); OAuthUtils.validateMessage(oAuthMessage, client, accessToken, dataProvider, validator); } else { String consumerKey = null; String consumerSecret = null; String authHeader = oAuthMessage.getHeader("Authorization"); if (authHeader != null) { if (authHeader.startsWith("OAuth")) { consumerKey = oAuthMessage.getParameter(OAuth.OAUTH_CONSUMER_KEY); consumerSecret = oAuthMessage.getParameter(OAuthConstants.OAUTH_CONSUMER_SECRET); } else if (authHeader.startsWith("Basic")) { AuthorizationPolicy policy = getAuthorizationPolicy(authHeader); if (policy != null) { consumerKey = policy.getUserName(); consumerSecret = policy.getPassword(); } } } if (consumerKey != null) { client = dataProvider.getClient(consumerKey); } if (client == null) { LOG.warning("Client is invalid"); throw new OAuthProblemException(OAuth.Problems.CONSUMER_KEY_UNKNOWN); } if (consumerSecret != null && !consumerSecret.equals(client.getSecretKey())) { LOG.warning("Client secret is invalid"); throw new OAuthProblemException(OAuth.Problems.CONSUMER_KEY_UNKNOWN); } else { OAuthUtils.validateMessage(oAuthMessage, client, null, dataProvider, validator); } accessToken = client.getPreAuthorizedToken(); if (accessToken == null || !accessToken.isPreAuthorized()) { LOG.warning("Preauthorized access token is unavailable"); throw new OAuthProblemException(OAuth.Problems.TOKEN_REJECTED); } } List<OAuthPermission> permissions = accessToken.getScopes(); List<OAuthPermission> matchingPermissions = new ArrayList<>(); for (OAuthPermission perm : permissions) { boolean uriOK = checkRequestURI(req, perm.getUris()); boolean verbOK = checkHttpVerb(req, perm.getHttpVerbs()); if (uriOK && verbOK) { matchingPermissions.add(perm); } } if (!permissions.isEmpty() && matchingPermissions.isEmpty()) { String message = "Client has no valid permissions"; LOG.warning(message); throw new OAuthProblemException(message); } return new OAuthInfo(accessToken, matchingPermissions); } protected AuthorizationPolicy getAuthorizationPolicy(String authorizationHeader) { Message m = PhaseInterceptorChain.getCurrentMessage(); return m != null ? (AuthorizationPolicy)m.get(AuthorizationPolicy.class) : null; } protected boolean checkHttpVerb(HttpServletRequest req, List<String> verbs) { if (!verbs.isEmpty() && !verbs.contains(req.getMethod())) { String message = "Invalid http verb"; LOG.fine(message); return false; } return true; } protected boolean checkRequestURI(HttpServletRequest request, List<String> uris) { if (uris.isEmpty()) { return true; } String servletPath = request.getPathInfo(); boolean foundValidScope = false; for (String uri : uris) { if (OAuthUtils.checkRequestURI(servletPath, uri)) { foundValidScope = true; break; } } if (!foundValidScope) { String message = "Invalid request URI: " + request.getRequestURL().toString(); LOG.warning(message); } return foundValidScope; } protected SecurityContext createSecurityContext(HttpServletRequest request, final OAuthInfo info) { // TODO: // This custom parameter is only needed by the "oauth" // demo shipped in the distribution; needs to be removed. request.setAttribute("oauth_authorities", info.getRoles()); UserSubject subject = info.getToken().getSubject(); final UserSubject theSubject = subject; return new SecurityContext() { public Principal getUserPrincipal() { String login = AbstractAuthFilter.this.useUserSubject ? (theSubject != null ? theSubject.getLogin() : null) : info.getToken().getClient().getLoginName(); return new SimplePrincipal(login); } public boolean isUserInRole(String role) { List<String> roles = null; if (AbstractAuthFilter.this.useUserSubject && theSubject != null) { roles = theSubject.getRoles(); } else { roles = info.getRoles(); } return roles.contains(role); } }; } protected OAuthContext createOAuthContext(OAuthInfo info) { UserSubject subject = null; if (info.getToken() != null) { subject = info.getToken().getSubject(); } return new OAuthContext(subject, info.getMatchedPermissions()); } public void setValidator(OAuthValidator validator) { this.validator = validator; } public void setSupportUnknownParameters(boolean supportUnknownParameters) { this.supportUnknownParameters = supportUnknownParameters; } private class CustomHttpServletWrapper extends HttpServletRequestWrapper { CustomHttpServletWrapper(HttpServletRequest req) { super(req); } public Map<String, String[]> getParameterMap() { Map<String, String[]> params = super.getParameterMap(); if (supportUnknownParameters || ALLOWED_OAUTH_PARAMETERS.containsAll(params.keySet())) { return params; } Map<String, String[]> newParams = new HashMap<>(); for (Map.Entry<String, String[]> entry : params.entrySet()) { if (ALLOWED_OAUTH_PARAMETERS.contains(entry.getKey())) { newParams.put(entry.getKey(), entry.getValue()); } } return newParams; } } }