package net.sf.gazpachoquest.security.shiro;
import java.security.SignatureException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.Period;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Date;
import java.util.Locale;
import java.util.Set;
import javax.persistence.PersistenceException;
import net.sf.gazpachoquest.domain.support.Permission;
import net.sf.gazpachoquest.domain.user.Role;
import net.sf.gazpachoquest.domain.user.User;
import net.sf.gazpachoquest.qbe.SearchParameters;
import net.sf.gazpachoquest.security.support.HMACSignature;
import net.sf.gazpachoquest.services.UserService;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AccountException;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class JPARealm extends AuthorizingRealm {
public final static Logger logger = LoggerFactory.getLogger(JPARealm.class);
@Autowired
private UserService userService;
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
HmacAuthToken upToken = (HmacAuthToken) token;
String apiKey = upToken.getApiKey();
// Null apikey is invalid
if (apiKey == null) {
throw new AccountException("API Key is required");
}
User example = new User.Builder().apiKey(apiKey).build();
User user = null;
String secret = null;
try {
user = userService.findOneByExample(example, new SearchParameters().caseSensitive()).orElseThrow(
() -> new UnknownAccountException(String.format("No account found for apikey [%s]", apiKey)));
secret = user.getSecret();
} catch (PersistenceException e) {
final String message = "There was a SQL error while authenticating apikey [" + apiKey + "]";
// Rethrow any SQL errors as an authentication exception
throw new AuthenticationException(message, e);
}
String message = upToken.getMessage();
String dateUTC = upToken.getDateUTC();
verifyDate(dateUTC);
String expectedSignature = upToken.getSignature();
verifySignature(secret, message, expectedSignature);
logger.info("Authentication successfully for user {}", user.getFullName());
return AuthenticationInfoImpl.with().apiKey(apiKey).principal(user).build();
}
@Override
public boolean supports(AuthenticationToken token) {
return token.getClass().isAssignableFrom(HmacAuthToken.class);
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// null usernames are invalid
if (principals == null) {
throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
}
User user = (User) getAvailablePrincipal(principals);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Set<Role> roles = userService.getRoles(user.getId());
for (Role role : roles) {
info.addRole(role.getName());
}
Set<Permission<?>> permissions = userService.getPermissions(user.getId());
for (Permission<?> permission : permissions) {
info.addStringPermission(permission.getLiteral());
}
return info;
}
private void verifyDate(String dateUTC) {
if (StringUtils.isBlank(dateUTC)) {
logger.debug("Skiping date validation. No date header found in request");
return;
}
LocalDateTime dateTime = null;
try {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss Z").withLocale(Locale.ENGLISH);
// SimpleDateFormat sdf = new
// SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.ENGLISH);
// date = sdf.parse(dateUTC);
dateTime = LocalDateTime.parse(dateUTC, formatter);
// TODO waiting for common-lang-3.3.1
// DateFormatUtils.SMTP_DATETIME_FORMAT.parse(dateUTC);
} catch (DateTimeParseException e) {
throw new AuthenticationException("Date format invalid");
}
LocalDateTime start = LocalDateTime.now().minusMinutes(5);
LocalDateTime end = LocalDateTime.now().plusMinutes(5);
if (dateTime.isBefore(start) || dateTime.isAfter(end)) {
throw new AuthenticationException("Date not valid. Too old or too far in future");
}
}
private void verifySignature(String secret, String message, String expectedSignature) {
try {
logger.debug("Verifing signature... signedContent = {}", message);
String signature = HMACSignature.calculateRFC2104HMAC(message, secret);
if (!signature.equals(expectedSignature)) {
throw new AuthenticationException("Message Signature invalid");
}
} catch (SignatureException e) {
throw new AuthenticationException("Errors verifing hmac signature", e);
}
}
}