/* * Copyright (C) 2014 Stichting Akvo (Akvo Foundation) * * This file is part of Akvo FLOW. * * Akvo FLOW is free software: you can redistribute it and modify it under the terms of * the GNU Affero General Public License (AGPL) as published by the Free Software Foundation, * either version 3 of the License or any later version. * * Akvo FLOW 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 Affero General Public License included below for more details. * * The full license text can also be seen at <http://www.gnu.org/licenses/agpl.html>. */ package org.waterforpeople.mapping.app.web.rest.security; import java.util.Date; import java.util.Map; import javax.inject.Inject; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.waterforpeople.mapping.app.web.rest.security.user.ApiUser; import com.gallatinsystems.common.util.MD5Util; import com.gallatinsystems.user.dao.UserDao; import com.gallatinsystems.user.domain.User; public class ApiAuthenticationProvider implements AuthenticationProvider { @Inject UserDao userDao; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { @SuppressWarnings("unchecked") Map<String, String> details = (Map<String, String>) authentication .getDetails(); String[] credentials = parseCredentials(details.get("Authorization")); String accessKey = credentials[0]; String clientSignature = credentials[1]; ApiUser apiUser = findUser(accessKey); if (apiUser == null) { throw new BadCredentialsException("Authorization Required"); } Date date = parseDate(details.get("Date")); long clientTime = date.getTime(); long serverTime = new Date().getTime(); long timeDelta = 600000; // +/- 10 minutes if (serverTime - timeDelta < clientTime && clientTime < serverTime + timeDelta) { String payload = buildPayload(details.get("HTTP-Verb"), date, details.get("Resource")); String serverSignature = MD5Util.generateHMAC(payload, apiUser.getSecret()); if (clientSignature.equals(serverSignature)) { // Successful authentication return new ApiUserAuthentication(apiUser); } } // Unsuccessful authentication throw new BadCredentialsException("Authorization Required"); } private ApiUser findUser(String accessKey) { User user = userDao.findByAccessKey(accessKey); if (user != null) { return new ApiUser(user.getUserName(), user.getAccessKey(), user.getSecret(), user.getKey().getId()); } else { return null; } } @Override public boolean supports(Class<?> authentication) { return true; } private String[] parseCredentials(String credentialsString) { if (credentialsString == null) throw new BadCredentialsException("Authorization required"); String[] credentials = credentialsString.split(":"); if (credentials.length != 2) { throw new BadCredentialsException("Authorization required"); } credentials[0] = credentials[0].trim(); credentials[1] = credentials[1].trim(); return credentials; } private Date parseDate(String dateString) { try { // epoch is seconds based. Date constructor is milliseconds based. return new Date(Long.parseLong(dateString) * 1000); } catch (NumberFormatException e) { throw new BadCredentialsException("Authorization Required"); } } private String buildPayload(String httpVerb, Date date, String resource) { // date.getTime() is millisecond based and epoch is seconds based return httpVerb + "\n" + String.valueOf(date.getTime() / 1000) + "\n" + resource; } }