blob: c1ff8f525740a4836a55ad0ab97fde1ab34c2eb0 [file] [log] [blame]
/*
* Copyright 2017 The Mifos Initiative.
*
* 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 io.mifos.identity.rest;
import io.mifos.anubis.annotation.AcceptedTokenType;
import io.mifos.anubis.annotation.Permittable;
import io.mifos.anubis.api.v1.TokenConstants;
import io.mifos.anubis.security.AmitAuthenticationException;
import io.mifos.core.command.domain.CommandCallback;
import io.mifos.core.command.domain.CommandProcessingException;
import io.mifos.core.command.gateway.CommandGateway;
import io.mifos.core.lang.ServiceException;
import io.mifos.identity.api.v1.PermittableGroupIds;
import io.mifos.identity.api.v1.domain.Authentication;
import io.mifos.identity.internal.command.AuthenticationCommandResponse;
import io.mifos.identity.internal.command.RefreshTokenAuthenticationCommand;
import io.mifos.identity.internal.command.PasswordAuthenticationCommand;
import io.mifos.identity.internal.util.IdentityConstants;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.util.WebUtils;
import javax.annotation.Nullable;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.ExecutionException;
/**
* @author Myrle Krantz
*/
@SuppressWarnings("unused")
@RestController //
public class AuthorizationRestController {
private final CommandGateway commandGateway;
private final Logger logger;
//Whether the cookie can only be transported via https. Should only be set to false for testing.
@Value("${identity.token.refresh.secureCookie:true}")
private boolean secureRefreshTokenCookie;
@Value("${server.contextPath}")
private String contextPath;
@Autowired public AuthorizationRestController(
final CommandGateway commandGateway,
@Qualifier(IdentityConstants.LOGGER_NAME) final Logger logger) {
super();
this.commandGateway = commandGateway;
this.logger = logger;
}
@RequestMapping(
value = "/token",
method = RequestMethod.POST,
consumes = {MediaType.ALL_VALUE},
produces = {MediaType.APPLICATION_JSON_VALUE}
)
@Permittable(AcceptedTokenType.GUEST)
public
@ResponseBody ResponseEntity<Authentication> authenticate(
final HttpServletResponse response,
final HttpServletRequest request,
@RequestParam("grant_type") final String grantType,
@RequestParam(value = "username", required = false) final String username,
@RequestParam(value = "password", required = false) final String password,
@RequestParam(value = "refresh_token", required = false) final String refreshTokenParam) throws InterruptedException {
switch (grantType) {
case "refresh_token": {
final String refreshToken = getRefreshToken(refreshTokenParam, request);
try {
final AuthenticationCommandResponse authenticationCommandResponse
= getAuthenticationCommandResponse(new RefreshTokenAuthenticationCommand(refreshToken));
final Authentication ret = map(authenticationCommandResponse, response);
return new ResponseEntity<>(ret, HttpStatus.OK);
}
catch (final AmitAuthenticationException e)
{
return new ResponseEntity<>(HttpStatus.FORBIDDEN);
}
}
case "password": {
if (username == null)
throw ServiceException.badRequest("The query parameter username must be set if the grant_type is password.");
if (password == null)
throw ServiceException.badRequest("The query parameter password must be set if the grant_type is password.");
try {
final Authentication ret = map(getAuthenticationCommandResponse(
new PasswordAuthenticationCommand(username, password)), response);
return new ResponseEntity<>(ret, HttpStatus.OK);
}
catch (final AmitAuthenticationException e)
{
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
default:
throw ServiceException.badRequest("invalid grant type: " + grantType);
}
}
@RequestMapping(value = "/token/_current", method = RequestMethod.DELETE,
consumes = {MediaType.ALL_VALUE},
produces = {MediaType.APPLICATION_JSON_VALUE})
@Permittable(value = AcceptedTokenType.TENANT, groupId = PermittableGroupIds.SELF_MANAGEMENT)
@ResponseBody ResponseEntity<Void> logout(
final HttpServletResponse response,
final HttpServletRequest request)
{
response.addCookie(bakeRefreshTokenCookie(""));
return ResponseEntity.ok().build();
}
private String getRefreshToken(final @Nullable String refreshTokenParam, final HttpServletRequest request) {
if (refreshTokenParam != null)
return refreshTokenParam;
final Cookie refreshTokenCookie = WebUtils.getCookie(request, TokenConstants.REFRESH_TOKEN_COOKIE_NAME);
if (refreshTokenCookie == null)
throw ServiceException.badRequest("One (and only one) refresh token cookie must be included in the request if the grant_type is refresh_token");
return refreshTokenCookie.getValue();
}
private AuthenticationCommandResponse getAuthenticationCommandResponse(
final Object authenticationCommand) throws AmitAuthenticationException, InterruptedException {
try
{
final CommandCallback<AuthenticationCommandResponse> ret =
commandGateway.process(authenticationCommand,
AuthenticationCommandResponse.class);
return ret.get();
}
catch (final ExecutionException e)
{
if (AmitAuthenticationException.class.isAssignableFrom(e.getCause().getClass()))
{
throw AmitAuthenticationException.class.cast(e.getCause());
}
else if (CommandProcessingException.class.isAssignableFrom(e.getCause().getClass()))
{
final CommandProcessingException commandProcessingException = (CommandProcessingException) e.getCause();
if (ServiceException.class.isAssignableFrom(commandProcessingException.getCause().getClass()))
throw (ServiceException)commandProcessingException.getCause();
else {
logger.error("Authentication failed with an unexpected error.", e);
throw ServiceException.internalError("An error occurred while attempting to authenticate a user.");
}
}
else if (ServiceException.class.isAssignableFrom(e.getCause().getClass()))
{
throw (ServiceException)e.getCause();
}
else {
logger.error("Authentication failed with an unexpected error.", e);
throw ServiceException.internalError("An error occurred while attempting to authenticate a user.");
}
}
catch (final CommandProcessingException e)
{
logger.error("Authentication failed with an unexpected error.", e);
throw ServiceException.internalError("An error occurred while attempting to authenticate a user.");
}
}
private Authentication map(
final AuthenticationCommandResponse commandResponse,
final HttpServletResponse httpServletResponse)
{
httpServletResponse.addCookie(bakeRefreshTokenCookie(commandResponse.getRefreshToken()));
return new Authentication(
commandResponse.getAccessToken(),
commandResponse.getAccessTokenExpiration(),
commandResponse.getRefreshTokenExpiration(),
commandResponse.getPasswordExpiration());
}
private Cookie bakeRefreshTokenCookie(final String refreshToken) {
final Cookie refreshTokenCookie = new Cookie(TokenConstants.REFRESH_TOKEN_COOKIE_NAME, refreshToken);
refreshTokenCookie.setSecure(secureRefreshTokenCookie);
refreshTokenCookie.setHttpOnly(true);
refreshTokenCookie.setPath(contextPath + "/token");
return refreshTokenCookie;
}
}