Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
import java.util.Map;
import java.util.Set;
import javax.jcr.AccessDeniedException;
import javax.jcr.InvalidItemStateException;
import javax.jcr.ItemNotFoundException;
import javax.jcr.NamespaceRegistry;
import javax.jcr.Node;
import javax.jcr.Property;
Expand Down Expand Up @@ -76,6 +78,13 @@ public class TokenProvider extends ProtectedItemModifier {

private static final char DELIM = '_';

/**
* Number of attempts to persist a new token node before giving up. Concurrent logins
* of the same user write below a shared token parent and may invalidate each other's
* pending changes (JCR-5095).
*/
private static final int CREATE_TOKEN_MAX_ATTEMPTS = 3;

private static final Set<String> RESERVED_ATTRIBUTES = new HashSet(3);
static {
RESERVED_ATTRIBUTES.add(TOKEN_ATTRIBUTE);
Expand Down Expand Up @@ -146,6 +155,39 @@ public TokenInfo createToken(User user, SimpleCredentials sc) throws RepositoryE
*/
private TokenInfo createToken(User user, Map<String, ?> attributes) throws RepositoryException {
String error = "Failed to create login token. ";
// Concurrent logins of the same user add token nodes below the very same token
// parent. A concurrent commit below that parent may invalidate the pending changes
// of this session, so that the token node can neither be saved
// (InvalidItemStateException) nor resolved afterwards (ItemNotFoundException while
// building its path). Both are transient, so retry with a refreshed session,
// analogous to the conflict handling in getTokenParent (JCR-5095).
for (int attempt = 1; ; attempt++) {
try {
return createTokenNode(user, attributes, error);
} catch (InvalidItemStateException | ItemNotFoundException e) {
if (attempt >= CREATE_TOKEN_MAX_ATTEMPTS) {
throw e;
}
log.debug("Conflict while creating login token (attempt {}) -> retrying", attempt, e);
// discard the token node that could not be persisted before retrying
session.refresh(false);
}
}
}

/**
* Creates and persists a single token node below the token parent of the given user.
*
* @param user The user for which a new token should be created.
* @param attributes The attributes associated with the new token.
* @param error Prefix used for log messages.
* @return A new {@code TokenInfo} or {@code null} if the token could not be created.
* @throws InvalidItemStateException If the token node could not be persisted because
* the token parent was modified concurrently.
* @throws ItemNotFoundException If the token node could not be resolved after saving
* because the token parent was modified concurrently.
*/
private TokenInfo createTokenNode(User user, Map<String, ?> attributes, String error) throws RepositoryException {
NodeImpl tokenParent = getTokenParent(user);
if (tokenParent != null) {
try {
Expand Down
Loading