io.imunity.fido.service.UnityFidoRegistrationStorage Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of unity-server-fido Show documentation
Show all versions of unity-server-fido Show documentation
Fido credential management and authentication
/*
* Copyright (c) 2020 Bixbit - Krzysztof Benedyczak All rights reserved.
* See LICENCE.txt file for licensing information.
*/
package io.imunity.fido.service;
import com.yubico.webauthn.CredentialRepository;
import com.yubico.webauthn.RegisteredCredential;
import com.yubico.webauthn.data.ByteArray;
import com.yubico.webauthn.data.PublicKeyCredentialDescriptor;
import io.imunity.fido.credential.FidoCredentialInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import pl.edu.icm.unity.base.entity.EntityParam;
import pl.edu.icm.unity.base.exceptions.EngineException;
import pl.edu.icm.unity.base.utils.Log;
import pl.edu.icm.unity.engine.api.attributes.AttributeSupport;
import pl.edu.icm.unity.engine.api.authn.EntityWithCredential;
import pl.edu.icm.unity.engine.api.identity.IdentityResolver;
import pl.edu.icm.unity.engine.credential.CredentialAttributeTypeProvider;
import java.util.AbstractMap;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import static java.util.Objects.isNull;
/**
* Implements CredentialRepository to provide users data required by yubico library.
*
* @author R. Ledzinski
*/
class UnityFidoRegistrationStorage implements CredentialRepository
{
private static final org.apache.logging.log4j.Logger log = Log.getLogger(Log.U_SERVER_FIDO, UnityFidoRegistrationStorage.class);
private final FidoEntityHelper entityHelper;
private final IdentityResolver identityResolver;
private final String credentialName;
private final AttributeSupport attributeSupport;
public UnityFidoRegistrationStorage(final FidoEntityHelper entityHelper, final IdentityResolver identityResolver, AttributeSupport attributeSupport, final String credentialName)
{
this.entityHelper = entityHelper;
this.identityResolver = identityResolver;
this.attributeSupport = attributeSupport;
this.credentialName = credentialName;
}
@Override
public Set getCredentialIdsForUsername(final String username)
{
log.debug("Enter getCredentialIdsForUsername({})", username);
return getFidoCredentialInfoForUsername(username).stream()
.map(i -> PublicKeyCredentialDescriptor.builder()
.id(i.getCredentialId())
.build())
.collect(Collectors.toSet());
}
@Override
public Optional getUsernameForUserHandle(final ByteArray userHandle)
{
FidoUserHandle uh = new FidoUserHandle(userHandle.getBytes());
log.debug("getUsernameForUserHandle({})", uh.asString());
Optional un = entityHelper.getUsernameForUserHandle(uh.asString());
if (un.isPresent())
return un;
return getUsernameFromAllCredentials(uh.asString());
}
@Override
public Optional getUserHandleForUsername(final String username)
{
log.debug("getUserHandleForUsername({})", username);
return entityHelper.getUserHandleForUsername(username).map(uh -> new ByteArray(FidoUserHandle.fromString(uh).getBytes()));
}
List getFidoCredentialInfoForUserHandle(final String userHandle)
{
log.debug("getFidoCredentialInfoForUserHandle({})", userHandle);
Optional username = entityHelper.getUsernameForUserHandle(userHandle);
if (!username.isPresent())
return Collections.emptyList();
return getFidoCredentialInfoForEntity(entityHelper.resolveUsername(null, username.get()).orElseThrow(() -> new NoEntityException("No entity - should not happen!")));
}
List getFidoCredentialInfoForUsername(final String username)
{
log.debug("getFidoCredentialInfoForUsername({})", username);
Identities ids;
try
{
ids = entityHelper.resolveUsername(null, username).orElseThrow(() -> new NoEntityException("No entity - should not happen!"));
} catch (FidoException e) {
return Collections.emptyList();
}
return getFidoCredentialInfoForEntity(ids);
}
private List getFidoCredentialInfoForEntity(final Identities identities)
{
if (isNull(identities))
return Collections.emptyList();
EntityParam entityParam = identities.getEntityParam();
EntityWithCredential entity;
try
{
entity = identityResolver.resolveIdentity(entityParam.getIdentity().getValue(),
new String[]{entityParam.getIdentity().getTypeId()},
credentialName);
} catch (EngineException e)
{
log.error("Failed to resolve identity", e);
return Collections.emptyList();
}
return FidoCredentialInfo.deserializeList(entity.getCredentialValue());
}
@Override
public Optional lookup(final ByteArray credentialId, final ByteArray userHandle)
{
log.debug("Enter lookup()");
return getFidoCredentialInfoForUserHandle(new FidoUserHandle(userHandle.getBytes()).asString()).stream()
.filter(info -> info.getCredentialId().equals(credentialId))
.map(info -> info.getCredentialWithHandle(userHandle))
.findFirst();
}
@Override
public Set lookupAll(final ByteArray credentialId)
{
log.debug("Enter lookupAll()");
// FIXME used to make sure no other credential with given ID exists
return Collections.emptySet();
}
Optional getUsernameFromAllCredentials(String userHandle) {
Optional entityId = attributeSupport.getEntitiesWithAttributes(CredentialAttributeTypeProvider.CREDENTIAL_PREFIX + credentialName).entrySet().stream()
.filter(e -> !e.getValue().isEmpty() && !e.getValue().get(0).getValues().isEmpty())
.map(e -> new AbstractMap.SimpleEntry<>(e.getKey(), e.getValue().get(0).getValues().get(0)))
.filter(e -> FidoCredentialInfo.deserializeList(e.getValue()).stream().anyMatch(c -> c.getUserHandle().equals(userHandle)))
.map(Map.Entry::getKey)
.findAny();
log.debug("getUsernameFromAllCredentials(): found={}", entityId.isPresent());
return entityId.flatMap(id -> {
Optional resolved = entityHelper.resolveUsername(id, null);
resolved.ifPresent(r -> entityHelper.getOrCreateUserHandle(r, userHandle));
return resolved.map(r -> r.getUsername());
});
}
/**
* Factory and cache that creates Fido registration storage used mainly by Yubico library.
*/
@Component
public static class UnityFidoRegistrationStorageCache
{
private Map cache = new ConcurrentHashMap<>();
private FidoEntityHelper entityHelper;
private IdentityResolver identityResolver;
private AttributeSupport attributeSupport;
@Autowired
public UnityFidoRegistrationStorageCache(final FidoEntityHelper entityHelper, final IdentityResolver identityResolver, final AttributeSupport attributeSupport)
{
this.entityHelper = entityHelper;
this.identityResolver = identityResolver;
this.attributeSupport = attributeSupport;
}
UnityFidoRegistrationStorage getInstance(final String credentialName)
{
return cache.computeIfAbsent(credentialName, (name) -> new UnityFidoRegistrationStorage(entityHelper, identityResolver, attributeSupport, credentialName));
}
}
}