/* * Copyright (C) 2016-2019 Authlete, Inc. * * Licensed 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 com.authlete.jaxrs.server.api; import java.util.Arrays; import java.util.Date; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.glassfish.jersey.server.mvc.Viewable; import com.authlete.common.dto.AuthorizationResponse; import com.authlete.common.dto.Client; import com.authlete.common.types.Prompt; import com.authlete.common.types.SubjectType; import com.authlete.common.types.User; import com.authlete.jaxrs.AuthorizationDecisionHandler.Params; import com.authlete.jaxrs.AuthorizationPageModel; import com.authlete.jaxrs.spi.AuthorizationRequestHandlerSpiAdapter; /** * Implementation of {@link com.authlete.jaxrs.spi.AuthorizationRequestHandlerSpi * AuthorizationRequestHandlerSpi} interface which needs to be given * to the constructor of {@link com.authlete.jaxrs.AuthorizationRequestHandler * AuthorizationRequestHandler}. * * <p> * Note: The current implementation implements only {@link * #generateAuthorizationPage(AuthorizationResponse) generateAuthorizationPage()} * method. Other methods need to be implemented only when you want to support * {@code prompt=none} in authorization requests. See <a href= * "http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest">3.1.2.1. * Authentication Request</a> in <a href= * "http://openid.net/specs/openid-connect-core-1_0.html">OpenID Connect Core * 1.0</a> for details about {@code prompt=none}. * </p> * * @author Takahiko Kawasaki */ class AuthorizationRequestHandlerSpiImpl extends AuthorizationRequestHandlerSpiAdapter { /** * {@code "text/html;charset=UTF-8"} */ private static final MediaType MEDIA_TYPE_HTML = MediaType.TEXT_HTML_TYPE.withCharset("UTF-8"); /** * The page template to ask the resource owner for authorization. */ private static final String TEMPLATE = "/authorization"; /** * Authorization request to the authorization endpoint. */ private final HttpServletRequest mRequest; /** * Client associated with the authorization request. (Filled in during authorization response.) */ private Client mClient; /** * Constructor with an authorization request to the authorization endpoint. */ public AuthorizationRequestHandlerSpiImpl(HttpServletRequest request) { mRequest = request; } @Override public Response generateAuthorizationPage(AuthorizationResponse info) { // Create an HTTP session. HttpSession session = mRequest.getSession(true); // Store some variables into the session so that they can be // referred to later in AuthorizationDecisionEndpoint. session.setAttribute("params", Params.from(info)); session.setAttribute("acrs", info.getAcrs()); session.setAttribute("client", info.getClient()); mClient = info.getClient(); // update the client in case we need it with a no-interaction response // Clear the current user information in the session if necessary. clearCurrentUserInfoInSessionIfNecessary(info, session); // Get the user from the session if they exist. User user = (User)session.getAttribute("user"); // Prepare a model object which contains information needed to // render the authorization page. Feel free to create a subclass // of AuthorizationPageModel or define another different class // according to what you need in the authorization page. AuthorizationPageModel model = new AuthorizationPageModel(info, user); // Create a Viewable instance that represents the authorization // page. Viewable is a class provided by Jersey for MVC. Viewable viewable = new Viewable(TEMPLATE, model); // Create a response that has the viewable as its content. return Response.ok(viewable, MEDIA_TYPE_HTML).build(); } @Override public boolean isUserAuthenticated() { // Create an HTTP session. HttpSession session = mRequest.getSession(true); // Get the user from the session if they exist. User user = (User)session.getAttribute("user"); // If the user information exists in the session, the user is already // authenticated; Otherwise, the user is not authenticated. return user != null; } @Override public long getUserAuthenticatedAt() { // Create an HTTP session. HttpSession session = mRequest.getSession(true); // Get the user from the session if they exist. Date authTime = (Date)session.getAttribute("authTime"); if (authTime == null) { return 0; } return authTime.getTime() / 1000L; } @Override public String getUserSubject() { // Create an HTTP session. HttpSession session = mRequest.getSession(true); // Get the user from the session if they exist. User user = (User)session.getAttribute("user"); if (user == null) { return null; } return user.getSubject(); } private void clearCurrentUserInfoInSessionIfNecessary(AuthorizationResponse info, HttpSession session) { // Get the user from the session if they exist. User user = (User)session.getAttribute("user"); Date authTime = (Date)session.getAttribute("authTime"); if (user == null || authTime == null) { // The information about the user does not exist in the session. return; } // Check 'prompts'. checkPrompts(info, session); // Check 'authentication age'. checkAuthenticationAge(info, session, authTime); } private void checkPrompts(AuthorizationResponse info, HttpSession session) { if (info.getPrompts() == null) { return; } List<Prompt> prompts = Arrays.asList(info.getPrompts()); if (prompts.contains(Prompt.LOGIN)) { // Force a login by clearing out the current user. clearCurrentUserInfoInSession(session); }; } private void checkAuthenticationAge(AuthorizationResponse info, HttpSession session, Date authTime) { // TODO: max_age == 0 effectively means "log in the user interactively // now" but it's used here as a flag, we should fix this to use Integer // instead of int probably. if (info.getMaxAge() <= 0) { return; } Date now = new Date(); // Calculate number of seconds that have elapsed since login. long authAge = (now.getTime() - authTime.getTime()) / 1000L; if (authAge > info.getMaxAge()) { // Session age is too old, clear out the current user. clearCurrentUserInfoInSession(session); }; } private void clearCurrentUserInfoInSession(HttpSession session) { session.removeAttribute("user"); session.removeAttribute("authTime"); } @Override public String getSub() { if (mClient != null && mClient.getSubjectType() == SubjectType.PAIRWISE) { // it's a pairwise subject, calculate it here String sectorIdentifier = mClient.getDerivedSectorIdentifier(); return mClient.getSubjectType().name() + "-" + sectorIdentifier + "-" + getUserSubject(); } else { return null; } } }