/*
* Copyright (c) 2014 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.geo.service.impl.resource;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import com.emc.storageos.db.client.DbClient;
import com.emc.storageos.db.client.model.StorageOSUserDAO;
import com.emc.storageos.db.client.model.Token;
import com.emc.storageos.db.exceptions.DatabaseException;
import com.emc.storageos.security.authentication.TokenKeyGenerator.TokenKeysBundle;
import com.emc.storageos.geo.service.authentication.InterVDCHMACAuthFilter;
import com.emc.storageos.geomodel.TokenResponse;
import com.emc.storageos.geomodel.request.TokenKeysRequest;
import com.emc.storageos.security.authentication.CassandraTokenValidator;
import com.emc.storageos.security.authentication.InternalLogoutClient;
import com.emc.storageos.security.authentication.TokenKeyGenerator;
import com.emc.storageos.security.authentication.RequestProcessingUtils;
import com.emc.storageos.security.authentication.TokenValidator;
import com.emc.storageos.security.geo.RequestedTokenHelper;
import com.emc.storageos.security.geo.RequestedTokenHelper.Operation;
import com.emc.storageos.security.geo.TokenResponseBuilder;
import com.emc.storageos.svcs.errorhandling.resources.APIException;
/**
* Service to perform Token and user lookups to other VDCs, logouts...
* */
@Path(InterVDCHMACAuthFilter.INTERVDC_URI + "token")
public class TokenService {
private static Logger log = LoggerFactory.getLogger(TokenService.class);
@Autowired
private DbClient dbClient;
@Autowired
private TokenValidator tokenValidator;
@Autowired
private TokenKeyGenerator tokenKeyGenerator;
@Autowired
private RequestedTokenHelper tokenMapHelper;
@Autowired
private InternalLogoutClient internalLogoutClient;
/**
* Retrieves Token and UserDAO records from a passed in auth token (header)
* TokenKeysRequest can also contain key ids to look at. If they don't match the local
* TokenKeysBundle, send the updated bundle in the response
*
* @param httpRequest
* @return TokenResponse with token and userDAO records populated.
*/
@POST
@Consumes(MediaType.APPLICATION_XML)
@Produces(MediaType.APPLICATION_XML)
public TokenResponse getToken(@Context HttpServletRequest httpRequest, TokenKeysRequest req) {
String rawToken = httpRequest.getHeader(RequestProcessingUtils.AUTH_TOKEN_HEADER);
String firstKey = req.getFirstKeyId();
String secondKey = req.getSecondKeyId();
Token token = null;
StorageOSUserDAO user = null;
TokenKeysBundle updatedBundle = null;
// validate token if provided
if (StringUtils.isNotBlank(rawToken)) {
token = (Token) tokenValidator.verifyToken(rawToken);
if (token != null) {
user = tokenValidator.resolveUser(token);
}
if (user == null || token == null) {
throw APIException.unauthorized.noTokenFoundForUserFromForeignVDC();
}
if (user.getIsLocal()) {
throw APIException.forbidden.localUsersNotAllowedForSingleSignOn(user.getUserName());
}
}
// compare key ids to local tokenkeybundle if provided (prevKey will always be
// provided if a bundle was sent. CurKey may be null. In other words, there may
// not has been a rotation yet.
if (StringUtils.isNotBlank(firstKey)) {
try {
updatedBundle = tokenKeyGenerator.readBundle();
} catch (Exception ex) {
log.error("Could not look at local token keys bundle");
}
if (updatedBundle != null) { // if we found a bundle
log.debug("Read the local key bundle");
// look at its key ids
List<String> keyIds = updatedBundle.getKeyEntries();
if ((firstKey.equals(keyIds.get(0)) && secondKey == null && keyIds.size() == 1) ||
(firstKey.equals(keyIds.get(0)) && secondKey != null && secondKey.equals(keyIds.get(1)))) {
log.info("Key id match. Not returning a bundle");
// if they both match what was passed in, make the bundle null and
// return that. Caller has updated keys and does not need them.
updatedBundle = null;
} else {
log.info("Key ids do not match. Returning updated bundle");
}
}
}
if (token != null) {
tokenMapHelper.addOrRemoveRequestingVDC(Operation.ADD_VDC, token.getId().toString(),
req.getRequestingVDC());
// update idle time on original token. Since it is being borrowed by another vdc,
// it just got accessed.
token.setLastAccessTime(CassandraTokenValidator.getCurrentTimeInMins());
try {
dbClient.persistObject(token);
} catch (DatabaseException ex) {
log.error("failed updating last access time for borrowed token {}", token.getId());
}
}
return TokenResponseBuilder.buildTokenResponse(token, user, updatedBundle);
}
/**
*
* Makes an internal api call to authsvc to logout the token present in the request.
*
* @param httpRequest where to find the token
* @param force is the force(true/false) parameter that will be relayed to authsvc/logout
* @param username is the username parameter that will be relayed to authsvc/logout
* @return
*/
@POST
@Consumes(MediaType.APPLICATION_XML)
@Produces(MediaType.APPLICATION_XML)
@Path("/logout")
public Response logoutToken(@Context HttpServletRequest httpRequest,
@QueryParam("username") String username, @QueryParam("force") boolean force) {
boolean res = internalLogoutClient.logoutUser(username, httpRequest, force, false);
return Response.ok().build();
}
}