package io.cattle.platform.iaas.api.auth.identity;
import io.cattle.platform.api.auth.Identity;
import io.cattle.platform.api.auth.Policy;
import io.cattle.platform.core.constants.IdentityConstants;
import io.cattle.platform.core.model.ProjectMember;
import io.cattle.platform.iaas.api.auth.integration.IdentityNotFoundException;
import io.cattle.platform.iaas.api.auth.integration.external.ExternalServiceAuthProvider;
import io.cattle.platform.iaas.api.auth.integration.interfaces.IdentityProvider;
import io.cattle.platform.util.exception.ExceptionUtils;
import io.github.ibuildthecloud.gdapi.condition.Condition;
import io.github.ibuildthecloud.gdapi.context.ApiContext;
import io.github.ibuildthecloud.gdapi.exception.ClientVisibleException;
import io.github.ibuildthecloud.gdapi.factory.SchemaFactory;
import io.github.ibuildthecloud.gdapi.model.ListOptions;
import io.github.ibuildthecloud.gdapi.request.resource.impl.AbstractNoOpResourceManager;
import io.github.ibuildthecloud.gdapi.util.ResponseCodes;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import javax.inject.Inject;
import org.apache.cloudstack.managed.context.ManagedContext;
import org.apache.cloudstack.managed.context.impl.DefaultManagedContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class IdentityManager extends AbstractNoOpResourceManager {
private static final Log logger = LogFactory.getLog(IdentityManager.class);
private Map<String, IdentityProvider> identityProviders;
ExecutorService executorService;
@Inject
ExternalServiceAuthProvider externalAuthProvider;
@Override
public Class<?>[] getTypeClasses() {
return new Class<?>[]{Identity.class};
}
@SuppressWarnings("unchecked")
@Override
public Object listInternal(SchemaFactory schemaFactory, String type, Map<Object, Object> criteria, ListOptions options) {
if (criteria.get("id") != null) {
return Collections.singletonList(projectMemberToIdentity((String) criteria.get("id")));
}
if (criteria.containsKey("name")) {
Condition search = ((List<Condition>) criteria.get("name")).get(0);
return searchIdentites((String)search.getValue(), true);
}
if (criteria.containsKey("all")) {
Condition search = ((List<Condition>) criteria.get("all")).get(0);
return searchIdentites((String)search.getValue(), false);
}
Policy policy = (Policy) ApiContext.getContext().getPolicy();
return refreshIdentities(policy.getIdentities());
}
/**
* This method is used to update the cached identities from the policy, which are generated
* from the users current token, which is only required to be updated once every
* 16 hours.
*
* @param identities Set of identities from Policy.
* @return Returns a new set of identities after using the api to call out to external
* sources and update to the newest information. This request is expensive as it will
* make N requests one for each Passed in identity.
*/
private List<Identity> refreshIdentities(final Set<Identity> identities) {
final Collection<Callable<Identity>> identitiesToGet = new ArrayList<>();
final List<Identity> identitiesToReturn = new ArrayList<>();
final ApiContext context = ApiContext.getContext();
final ManagedContext managedContext = new DefaultManagedContext();
for (final Identity identity : identities) {
identitiesToGet.add(new Callable<Identity>() {
@Override
public Identity call() throws Exception {
try {
return managedContext.callWithContext(new Callable<Identity>() {
@Override
public Identity call() throws Exception {
ApiContext.setContext(context);
return projectMemberToIdentity(identity.getId());
}
});
} catch (Exception e) {
ExceptionUtils.throwRuntime("Error occurred while getting Identity: " + identity.getId(), e);
return null;
}
}
});
}
try {
for (Future<Identity> future:executorService.invokeAll(identitiesToGet)){
Identity newIdentity = future.get();
authorize(newIdentity);
identitiesToReturn.add(newIdentity);
}
} catch (InterruptedException e) {
logger.error("Interrupted when getting and Identities.", e);
throw new RuntimeException(e);
} catch (ExecutionException e) {
logger.error("Error when executing search for Identity.", e);
throw new RuntimeException(e);
}
return identitiesToReturn;
}
public Identity getIdentity(String id) {
return projectMemberToIdentity(id);
}
public Identity projectMemberToIdentity(String id) {
String[] split = id.split(":", 2);
if (split.length != 2) {
return null;
}
Identity identity = null;
boolean scopeMatch = false;
for (IdentityProvider identityProvider : identityProviders.values()) {
if (identityProvider.scopes().contains(split[0]) && identityProvider.isConfigured()) {
scopeMatch = true;
identity = identityProvider.getIdentity(split[1], split[0]);
break;
}
}
if(!scopeMatch && identity == null) {
if(externalAuthProvider.isConfigured()) {
identity = externalAuthProvider.getIdentity(split[1], split[0]);
}
}
return identity;
}
private List<Identity> searchIdentites(String name, boolean exactMatch) {
Set<Identity> identities = new HashSet<>();
for (IdentityProvider identityProvider : identityProviders.values()) {
if (identityProvider.isConfigured()) {
identities.addAll(identityProvider.searchIdentities(name, exactMatch));
}
}
if(externalAuthProvider.isConfigured()) {
identities.addAll(externalAuthProvider.searchIdentities(name, exactMatch));
}
return new ArrayList<>(identities);
}
public Identity projectMemberToIdentity(ProjectMember member){
if (member == null){
return null;
}
Identity gotIdentity;
gotIdentity = projectMemberToIdentity(member.getExternalIdType() + ':' + member.getExternalId());
if (gotIdentity == null){
gotIdentity = new Identity(member.getExternalIdType(), member.getExternalId(), member.getName(),
null, null, '(' + member.getExternalIdType().split("_")[1].toUpperCase() + " not found) " + member.getName());
}
return untransform(new Identity(gotIdentity, member.getRole(), String.valueOf(member.getProjectId())), false);
}
public Identity untransform(Identity identity, boolean error) {
Identity newIdentity = null;
boolean scopeMatch = false;
for (IdentityProvider identityProvider : identityProviders.values()) {
if (identityProvider.scopes().contains(identity.getExternalIdType()) && identityProvider.isConfigured()) {
scopeMatch = true;
newIdentity = identityProvider.untransform(identity);
break;
}
}
if (!scopeMatch) {
//get from external auth service
if (externalAuthProvider.isConfigured()) {
newIdentity = externalAuthProvider.untransform(identity);
}
}
if (error && newIdentity == null) {
throw new ClientVisibleException(ResponseCodes.BAD_REQUEST, IdentityConstants.INVALID_TYPE, "Identity externalIdType is invalid", null);
} else if (newIdentity == null){
return identity;
} else {
return newIdentity;
}
}
@Inject
public void setIdentityProviders(Map<String, IdentityProvider> identityProviders) {
this.identityProviders = identityProviders;
}
public void setExecutorService(ExecutorService executorService) {
this.executorService = executorService;
}
public Identity projectMemberToIdentity(Identity identity) {
Identity newIdentity = null;
boolean scopeMatch = false;
for (IdentityProvider identityProvider : identityProviders.values()) {
if (identityProvider.scopes().contains(identity.getExternalIdType()) && identityProvider.isConfigured()) {
scopeMatch = true;
newIdentity = identityProvider.transform(identity);
if (newIdentity == null) {
throw new IdentityNotFoundException(identity);
}
break;
}
}
if (!scopeMatch) {
//get from external auth service
if (externalAuthProvider.isConfigured()) {
newIdentity = externalAuthProvider.transform(identity);
}
}
if (newIdentity == null){
throw new ClientVisibleException(ResponseCodes.BAD_REQUEST, IdentityConstants.INVALID_TYPE, "Identity externalIdType is invalid", null);
}
return newIdentity;
}
}