/** * Copyright © 2015 Instituto Superior Técnico * * This file is part of Bennu OAuth. * * Bennu OAuth is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Bennu OAuth is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Bennu OAuth. If not, see <http://www.gnu.org/licenses/>. */ package org.fenixedu.bennu.oauth.jaxrs; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.Optional; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.container.ResourceInfo; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import org.fenixedu.bennu.core.domain.User; import org.fenixedu.bennu.core.security.Authenticate; import org.fenixedu.bennu.oauth.annotation.OAuthEndpoint; import org.fenixedu.bennu.oauth.domain.ApplicationUserSession; import org.fenixedu.bennu.oauth.domain.ExternalApplication; import org.fenixedu.bennu.oauth.domain.ExternalApplicationScope; import org.fenixedu.bennu.oauth.domain.ServiceApplication; import org.fenixedu.bennu.oauth.util.OAuthUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Strings; import com.google.gson.JsonObject; class BennuOAuthAuthorizationFilter implements ContainerRequestFilter { private static final Logger logger = LoggerFactory.getLogger(BennuOAuthAuthorizationFilter.class); @Context ResourceInfo requestInfo; @Context private HttpServletRequest httpRequest; @Override public void filter(ContainerRequestContext requestContext) throws IOException { String ipAddress = getIpAddress(); String accessToken = getAccessToken(requestContext); final OAuthEndpoint endpoint = requestInfo.getResourceMethod().getAnnotation(OAuthEndpoint.class); Optional<ServiceApplication> serviceApplication = extractServiceApplication(accessToken); if (endpoint.serviceOnly() && !serviceApplication.isPresent()) { requestContext.abortWith(Response.status(Status.NOT_FOUND).build()); return; } if (serviceApplication.isPresent()) { if (serviceApplication.get().isDeleted()) { sendError(requestContext, "accessTokenInvalidFormat", "Access Token not recognized."); return; } if (serviceApplication.get().isBanned()) { sendError(requestContext, "appBanned", "The application has been banned."); return; } if (!serviceApplication.get().hasServiceAuthorization(accessToken)) { requestContext.abortWith(Response.status(Status.NOT_FOUND).build()); return; } if (!Strings.isNullOrEmpty(ipAddress) && !serviceApplication.get().matchesIpAddress(ipAddress)) { requestContext.abortWith(Response.status(Status.FORBIDDEN).build()); return; } Optional<ExternalApplicationScope> scope = ExternalApplicationScope.forKey(endpoint.value()); if (!scope.isPresent() && endpoint.value().length > 0) { // endpoint has scope but no scope in domain sendError(requestContext, "invalidScope", "Application doesn't have permissions to this endpoint."); return; } if (scope.isPresent() && !serviceApplication.get().getScopesSet().contains(scope.get())) { sendError(requestContext, "invalidScope", "Application doesn't have permissions to this endpoint."); return; } return; } if (Authenticate.isLogged()) { logger.trace("Already logged in, proceeding..."); return; } Optional<ExternalApplicationScope> scope = ExternalApplicationScope.forKey(endpoint.value()); if (scope.isPresent()) { Optional<ApplicationUserSession> session = extractUserSession(accessToken); if (session.isPresent()) { ApplicationUserSession appUserSession = session.get(); ExternalApplication app = session.get().getApplicationUserAuthorization().getApplication(); if (app.isDeleted()) { sendError(requestContext, "accessTokenInvalidFormat", "Access Token not recognized."); return; } if (app.isBanned()) { sendError(requestContext, "appBanned", "The application has been banned."); return; } if (!app.getScopesSet().contains(scope.get())) { sendError(requestContext, "invalidScope", "Application doesn't have permissions to this getEndpoint()."); return; } if (!appUserSession.matchesAccessToken(accessToken)) { sendError(requestContext, "accessTokenInvalid", "Access Token doesn't match."); return; } if (!appUserSession.isAccessTokenValid()) { sendError(requestContext, "accessTokenExpired", "The access has expired. Please use the refresh token endpoint to generate a new one."); return; } User foundUser = appUserSession.getApplicationUserAuthorization().getUser(); if (foundUser.isLoginExpired()) { sendError(requestContext, "accessTokenInvalidFormat", "Access Token not recognized."); return; } Authenticate.mock(foundUser); } else { sendError(requestContext, "accessTokenInvalidFormat", "Access Token not recognized."); return; } } else { logger.debug("Scope '{}' is not defined!", (Object) endpoint.value()); requestContext.abortWith(Response.status(Status.NOT_FOUND).build()); } } private String getIpAddress() { if (httpRequest != null) { String xFor = null; xFor = httpRequest.getHeader("x-forwarded-for"); if (!Strings.isNullOrEmpty(xFor)) { return xFor; } return httpRequest.getRemoteAddr(); } return null; } private void sendError(ContainerRequestContext requestContext, String error, String errorDescription) { JsonObject json = new JsonObject(); json.addProperty("error", error); json.addProperty("error_description", errorDescription); requestContext.abortWith(Response.status(Status.UNAUTHORIZED).entity(json.toString()).type(MediaType.APPLICATION_JSON) .build()); } private Optional<ServiceApplication> extractServiceApplication(String accessToken) { if (Strings.isNullOrEmpty(accessToken)) { return Optional.empty(); } try { String fullToken = new String(Base64.getDecoder().decode(accessToken), StandardCharsets.UTF_8); String[] accessTokenBuilder = fullToken.split(":"); if (accessTokenBuilder.length != 2) { return Optional.empty(); } return OAuthUtils.getDomainObject(accessTokenBuilder[0], ServiceApplication.class); } catch (IllegalArgumentException iea) { return Optional.empty(); } } private Optional<ApplicationUserSession> extractUserSession(String accessToken) { if (Strings.isNullOrEmpty(accessToken)) { return Optional.empty(); } try { String fullToken = new String(Base64.getDecoder().decode(accessToken), StandardCharsets.UTF_8); String[] accessTokenBuilder = fullToken.split(":"); if (accessTokenBuilder.length != 2) { return Optional.empty(); } return OAuthUtils.getDomainObject(accessTokenBuilder[0], ApplicationUserSession.class); } catch (IllegalArgumentException iea) { return Optional.empty(); } } private String getAccessToken(ContainerRequestContext requestContext) { return getHeaderOrQueryParam(requestContext, OAuthUtils.ACCESS_TOKEN); } private String getAuthorizationHeader(ContainerRequestContext request) { String authorization = request.getHeaderString(HttpHeaders.AUTHORIZATION); if (authorization != null && authorization.startsWith(OAuthUtils.TOKEN_TYPE_HEADER_ACCESS_TOKEN)) { return authorization.substring(OAuthUtils.TOKEN_TYPE_HEADER_ACCESS_TOKEN.length()).trim(); } return null; } private String getHeaderOrQueryParam(ContainerRequestContext requestContext, String paramName) { String paramValue = getAuthorizationHeader(requestContext); if (!Strings.isNullOrEmpty(paramValue)) { return paramValue; } paramValue = requestContext.getHeaderString(paramName); if (Strings.isNullOrEmpty(paramValue)) { paramValue = requestContext.getUriInfo().getQueryParameters().getFirst(paramName); } return paramValue; } }