Package io.fathom.cloud.identity

Source Code of io.fathom.cloud.identity.LoginServiceImpl$ProjectSpec

package io.fathom.cloud.identity;

import io.fathom.cloud.CloudException;
import io.fathom.cloud.OpenstackExtension;
import io.fathom.cloud.ServiceType;
import io.fathom.cloud.identity.api.os.model.v2.V2AuthCredentials;
import io.fathom.cloud.identity.api.os.model.v3.Endpoint;
import io.fathom.cloud.identity.api.os.model.v3.Service;
import io.fathom.cloud.identity.model.AuthenticatedUser;
import io.fathom.cloud.identity.secrets.Secrets;
import io.fathom.cloud.identity.secrets.UserWithSecret;
import io.fathom.cloud.identity.services.IdentityService;
import io.fathom.cloud.identity.state.AuthRepository;
import io.fathom.cloud.openstack.client.identity.ChallengeResponses;
import io.fathom.cloud.protobuf.CloudCommons.TokenInfo;
import io.fathom.cloud.protobuf.CloudCommons.TokenScope;
import io.fathom.cloud.protobuf.IdentityModel.CredentialData;
import io.fathom.cloud.protobuf.IdentityModel.DomainData;
import io.fathom.cloud.protobuf.IdentityModel.ProjectData;
import io.fathom.cloud.protobuf.IdentityModel.ProjectRoles;
import io.fathom.cloud.protobuf.IdentityModel.UserData;
import io.fathom.cloud.server.auth.TokenAuth;
import io.fathom.cloud.server.auth.TokenService;
import io.fathom.cloud.server.model.Project;
import io.fathom.cloud.server.resources.ClientCertificate;
import io.fathom.cloud.server.resources.OpenstackDefaults;

import java.util.Date;
import java.util.List;

import javax.inject.Inject;
import javax.inject.Singleton;

import org.keyczar.AesKey;
import org.keyczar.KeyczarUtils;
import org.keyczar.exceptions.KeyczarException;
import org.keyczar.interfaces.KeyczarReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fathomdb.TimeSpan;
import com.fathomdb.extensions.ExtensionModule;
import com.fathomdb.extensions.Extensions;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.io.BaseEncoding;
import com.google.inject.persist.Transactional;
import com.google.protobuf.ByteString;

@Singleton
public class LoginServiceImpl implements LoginService {
    private static final Logger log = LoggerFactory.getLogger(LoginServiceImpl.class);

    protected static final TimeSpan TOKEN_VALIDITY = new TimeSpan("1h");

    @Inject
    Secrets secretService;

    @Inject
    AuthRepository authRepository;

    @Inject
    TokenService tokenService;

    @Inject
    IdentityService identityService;

    @Inject
    Extensions extensions;

    @Override
    public List<Service> buildServiceMap(String baseUrl, ProjectData project) {
        List<Service> services = Lists.newArrayList();

        // {
        // Service service = new Service();
        // services.add(service);
        //
        // service.id = service.name = "keystone";
        // service.type = ServiceType.IDENTITY.getType();
        //
        // addEndpoint(service, baseUrl + "/openstack/identity/v2.0");
        // }

        if (project != null) {
            Service service = new Service();
            services.add(service);

            service.id = service.name = "nova";
            service.type = ServiceType.COMPUTE.getType();

            addEndpoint(service, baseUrl + "/openstack/compute/" + project.getId());
        }

        if (project != null) {
            Service service = new Service();
            services.add(service);

            service.id = service.name = "trove";
            service.type = ServiceType.DBAAS.getType();

            addEndpoint(service, baseUrl + "/openstack/dbaas/" + project.getId());
        }

        // if (project != null) {
        // Service service = new Service();
        // services.add(service);
        //
        // service.id = service.name = "heat";
        // service.type = ServiceTypes.ORCHESTRATION;
        //
        // addEndpoint(service, baseUrl + "/openstack/orchestration/" +
        // project.getId());
        // }

        {
            Service service = new Service();
            services.add(service);

            service.name = "glance";
            service.type = ServiceType.IMAGE.getType();

            addEndpoint(service, baseUrl + "/openstack/images");
        }

        if (project != null) {
            Service service = new Service();
            services.add(service);

            service.name = "swift";
            service.type = ServiceType.OBJECT_STORE.getType();

            addEndpoint(service, baseUrl + "/openstack/storage/" + project.getId());
        }

        List<ServiceType> enabledServices = Lists.newArrayList();

        Project genericProject = new Project(project.getId());
        for (ExtensionModule extension : extensions.getExtensions()) {
            if (extension instanceof OpenstackExtension) {
                List<ServiceType> extensionServices = ((OpenstackExtension) extension).getServices(genericProject,
                        baseUrl);
                enabledServices.addAll(extensionServices);
            }
        }

        for (ServiceType serviceType : enabledServices) {
            Service service = new Service();
            services.add(service);

            service.name = serviceType.getName();
            service.id = service.name;
            service.type = serviceType.getType();

            String url = baseUrl + "/openstack/" + serviceType.getUrlSuffix();
            if (serviceType != ServiceType.IDENTITY) {
                // TODO: Transform to attribute on ServiceType?
                url += "/" + project.getId();
            } else {
                // Yuk... identity needs v2.0
                url += "/v2.0";
            }

            addEndpoint(service, url);
        }

        return services;

    }

    private void addEndpoint(Service service, String url) {

        if (service.endpoints == null) {
            service.endpoints = Lists.newArrayList();
        }

        for (String interfaceName : new String[] { "public", "internal", "admin" }) {
            Endpoint endpoint = new Endpoint();
            service.endpoints.add(endpoint);

            endpoint.id = service.id + "-" + interfaceName;
            endpoint.name = service.name;
            endpoint.interfaceName = interfaceName;
            endpoint.region = OpenstackDefaults.DEFAULT_REGION;
            endpoint.serviceId = service.id;
            endpoint.url = url;

            // Link link = new Link();
            // link.self = url;
            // endpoint.links.add(link);
        }
    }

    @Override
    @Transactional
    public AuthenticatedUser authenticate(V2AuthCredentials authRequest, ClientCertificate clientCertificate)
            throws CloudException {
        DomainData domain = null;
        UserWithSecret userWithSecret = null;

        log.info("V2 Auth request: " + authRequest);

        ProjectSpec projectSpec = new ProjectSpec();
        if (!Strings.isNullOrEmpty(authRequest.tenantId)) {
            projectSpec.projectId = Long.valueOf(authRequest.tenantId);
        }

        projectSpec.projectName = authRequest.tenantName;

        if (authRequest.passwordCredentials != null) {
            domain = identityService.getDefaultDomain();
            userWithSecret = authenticate(domain, authRequest.passwordCredentials.username,
                    authRequest.passwordCredentials.password);
        } else if (authRequest.tokenCredentials != null) {
            String tokenId = authRequest.tokenCredentials.id;

            TokenInfo tokenInfo = findTokenInfo(tokenId);
            if (tokenInfo == null) {
                return null;
            }

            userWithSecret = checkSecret(tokenInfo);

            domain = authRepository.getDomains().find(tokenInfo.getDomainId());
            if (domain == null) {
                throw new IllegalStateException();
            }

            if (projectSpec.projectId == 0 && projectSpec.projectName == null) {
                log.info("Token login with scope: {}", tokenInfo.getTokenScope());

                if (tokenInfo.getTokenScope() == TokenScope.Project) {
                    projectSpec.projectId = tokenInfo.getProjectId();
                    log.info("Set projectId to: {}", projectSpec.projectId);
                }
            }
            // This is weird, but valid with V3's deprecation of unscoped
            // tokens...
            // if (tokenInfo.getTokenScope() != TokenScope.Unscoped) {
            // log.warn("Use of scoped token in authenticate request: {} with {}",
            // authRequest, tokenInfo);
            // throw new IllegalStateException();
            // }

            // if (tokenInfo.hasProjectId()) {
            // projectSpec.projectId = tokenInfo.getProjectId();
            // projectSpec.projectName = null;
            // } else {
            // // Not sure what to do here..
            // throw new UnsupportedOperationException();
            // }
        } else if (authRequest.challengeResponse != null && clientCertificate != null) {
            domain = identityService.getDefaultDomain();

            ByteString response = ByteString.copyFrom(BaseEncoding.base64().decode(
                    authRequest.challengeResponse.response));
            ByteString challenge = ByteString.copyFrom(BaseEncoding.base64().decode(
                    authRequest.challengeResponse.challenge));
            userWithSecret = authenticate(domain, clientCertificate, challenge, response);
        }

        if (userWithSecret == null) {
            return null;
        }

        AuthenticatedUser user = toAuthenticationV2(domain, projectSpec, userWithSecret);
        return user;
    }

    @Override
    @Transactional
    public AuthenticatedUser authenticate(String tokenId) throws CloudException {
        TokenInfo tokenInfo = findTokenInfo(tokenId);
        if (tokenInfo == null) {
            return null;
        }

        return authenticate(tokenInfo);
    }

    @Override
    @Transactional
    public AuthenticatedUser authenticate(TokenInfo tokenInfo) throws CloudException {
        DomainData domain = findDomainFromToken(tokenInfo);

        UserWithSecret userWithSecret = checkSecret(tokenInfo);
        if (userWithSecret == null) {
            return null;
        }

        TokenScope scope = tokenInfo.getTokenScope();

        switch (scope) {
        case Domain:
            return buildDomainToken(domain, userWithSecret);

        case Project:
            return buildProjectToken(domain, tokenInfo.getProjectId(), userWithSecret);

        case Unscoped:
            throw new UnsupportedOperationException();

        default:
            throw new IllegalStateException();
        }
    }

    private UserWithSecret checkSecret(TokenInfo tokenInfo) throws CloudException {
        if (TokenAuth.hasExpired(tokenInfo)) {
            // This is treated the same as an invalid token
            return null;
        }

        UserData userData = authRepository.getUsers().find(tokenInfo.getUserId());
        if (userData == null) {
            return null;
        }

        UserWithSecret userWithSecret = null;
        try {
            userWithSecret = secretService.getFromToken(userData, tokenInfo);
        } catch (KeyczarException e) {
            log.info("Error while checking token secret", e);
        }

        if (userWithSecret == null) {
            return null;
        }

        return userWithSecret;
    }

    public static class ProjectSpec {
        public long projectId;
        public String projectName;

    }

    private AuthenticatedUser toAuthenticationV2(DomainData domain, ProjectSpec projectSpec,
            UserWithSecret userWithSecret) throws CloudException {
        ProjectData project = null;
        ProjectRoles projectRoles = null;

        UserData user = userWithSecret.getUserData();

        if (projectSpec.projectId != 0) {
            return buildProjectToken(domain, projectSpec.projectId, userWithSecret);
        } else if (!Strings.isNullOrEmpty(projectSpec.projectName)) {
            for (ProjectRoles i : user.getProjectRolesList()) {
                ProjectData p = authRepository.getProjects().find(i.getProject());
                if (p == null) {
                    continue;
                }

                if (projectSpec.projectName.equals(p.getName())) {
                    projectRoles = i;
                    project = p;
                    break;
                }
            }

            if (projectRoles == null) {
                return null;
            }

            return buildProjectToken(domain, project, userWithSecret);
        }

        // else if (user.hasDefaultProjectId()) {
        // long projectId = user.getDefaultProjectId();
        //
        // projectRoles = Users.findProjectRoles(user, projectId);
        //
        // if (projectRoles != null) {
        // project = authRepository.getProjects().find(projectId);
        // if (project == null) {
        // log.warn("Cannot find project {}", projectId);
        // projectRoles = null;
        // }
        // }
        //
        // if (projectRoles == null) {
        // // Not an error
        // log.info("User {} does not have access to their default project",
        // user.getId());
        // } else {
        // scope = TokenScope.Project;
        // }
        // }

        assert (project == null);

        // For V2, we treat the scope as a domain if it is unspecified
        return buildDomainToken(domain, userWithSecret);
    }

    protected UserWithSecret authenticate(DomainData domain, String username, String password) throws CloudException {
        if (Strings.isNullOrEmpty(username)) {
            return null;
        }

        if (Strings.isNullOrEmpty(password)) {
            return null;
        }

        CredentialData credential = authRepository.getUsernames(domain).find(username);
        if (credential == null) {
            return null;
        }

        UserData user = authRepository.getUsers().find(credential.getUserId());
        if (user == null) {
            return null;
        }

        UserWithSecret userWithSecret = secretService.checkPassword(user, credential, password);
        if (userWithSecret == null) {
            // TODO: Throttle?
            log.debug("Password mismatch for {}", username);
            return null;
        }

        return userWithSecret;
    }

    @Override
    @Transactional
    public void changePassword(DomainData domain, String username, String password, KeyczarReader recoveryKey)
            throws CloudException {
        if (Strings.isNullOrEmpty(username)) {
            throw new IllegalArgumentException();
        }

        if (Strings.isNullOrEmpty(password)) {
            throw new IllegalArgumentException();
        }

        CredentialData credential = authRepository.getUsernames(domain).find(username);
        if (credential == null) {
            throw new IllegalArgumentException();
        }

        UserData user = authRepository.getUsers().find(credential.getUserId());
        if (user == null) {
            throw new IllegalArgumentException();
        }

        secretService.changePassword(user, credential, password, recoveryKey);
    }

    protected TokenInfo findTokenInfo(String tokenId) {
        try {
            TokenInfo tokenInfo = tokenService.findValidToken(tokenId);
            return tokenInfo;
        } catch (Exception e) {
            log.warn("Unexpected error while reading token", e);
            return null;
        }
    }

    // protected User findUserFromToken(TokenInfo tokenInfo) throws
    // CloudException {
    // if (tokenInfo == null) {
    // return null;
    // }
    //
    // if (TokenAuth.hasExpired(tokenInfo)) {
    // // This is treated the same as an invalid token
    // return null;
    // }
    //
    // long userId = tokenInfo.getUserId();
    // return new User(userId);
    // // authStore.getUsers().find(userId);
    // }

    protected DomainData findDomainFromToken(TokenInfo tokenInfo) throws CloudException {
        if (tokenInfo == null) {
            return null;
        }

        long domainId = -1;
        switch (tokenInfo.getTokenScope()) {
        case Domain:
            domainId = tokenInfo.getDomainId();
            break;

        case Project:
            if (tokenInfo.hasDomainId()) {
                domainId = tokenInfo.getDomainId();
            } else {
                // boolean resolveFromProject = true;
                // if (resolveFromProject) {
                // log.warn("Resolving domain from project for token");
                //
                // long projectId = tokenInfo.getProjectId();
                // ProjectData project =
                // authRepository.getProjects().find(projectId);
                // if (project != null) {
                // domainId = project.getDomainId();
                // }
                // }
            }

            if (domainId == -1 || domainId == 0) {
                throw new UnsupportedOperationException("No domain set in project-scoped token");
            }
            break;

        default:
            break;
        }

        if (domainId >= 0) {
            return authRepository.getDomains().find(domainId);
        } else {
            return null;
        }
    }

    @Override
    public TokenInfo buildTokenInfo(AuthenticatedUser authentication) {
        TokenScope tokenScope = authentication.getScope();

        Date expiration = TOKEN_VALIDITY.addTo(new Date());

        TokenInfo.Builder token = TokenInfo.newBuilder();
        token.setUserId(authentication.getUserId());
        // Millisecond resolution is overkill
        // (and size matters, because this token gets passed as a cookie))
        token.setExpiration(expiration.getTime() / 1000L);

        token.setTokenScope(tokenScope);

        ProjectData project = authentication.getProject();
        if (project != null) {
            token.setProjectId(project.getId());
            if (authentication.getProjectRoleIds() != null) {
                for (long projectRoleId : authentication.getProjectRoleIds()) {
                    token.addRoles(projectRoleId);
                }
            }
        }

        token.setDomainId(authentication.getDomainId());
        for (long domainRoleId : authentication.getDomainRoleIds(authentication.getDomainId())) {
            token.addDomainRoles(domainRoleId);
        }

        {
            ByteString tokenSecret = secretService.buildTokenSecret(authentication);
            token.setTokenSecret(tokenSecret);
        }
        return token.build();

    }

    @Override
    @Transactional
    public AuthenticatedUser authenticate(Long projectId, String username, String password) throws CloudException {
        DomainData domain = identityService.getDefaultDomain();
        UserWithSecret userWithSecret = authenticate(domain, username, password);
        if (userWithSecret == null) {
            return null;
        }

        if (projectId == null) {
            return buildDomainToken(domain, userWithSecret);
        } else {
            return buildProjectToken(domain, projectId, userWithSecret);
        }
    }

    @Override
    @Transactional
    public AuthenticatedUser authenticate(Long projectId, ClientCertificate clientCertificate, ByteString challenge,
            ByteString response) throws CloudException {
        DomainData domain = identityService.getDefaultDomain();
        UserWithSecret userWithSecret = authenticate(domain, clientCertificate, challenge, response);
        if (userWithSecret == null) {
            return null;
        }

        if (projectId == null) {
            return buildDomainToken(domain, userWithSecret);
        } else {
            return buildProjectToken(domain, projectId, userWithSecret);
        }
    }

    @Override
    @Transactional
    public ByteString getChallenge(ClientCertificate clientCertificate) throws CloudException {
        DomainData domain = identityService.getDefaultDomain();

        String keyId = toCredentialKey(clientCertificate.getPublicKeySha1());
        CredentialData credential = authRepository.getPublicKeyCredentials(domain.getId()).find(keyId);
        if (credential == null) {
            log.info("No credential found for {}", keyId);
            return null;
        }

        UserData user = authRepository.getUsers().find(credential.getUserId());
        if (user == null) {
            log.warn("User not found for credential {}", credential);
            return null;
        }

        return secretService.buildAuthChallenge(user, credential, clientCertificate);
    }

    private UserWithSecret authenticate(DomainData domain, ClientCertificate clientCertificate, ByteString challenge,
            ByteString response) throws CloudException {
        String keyId = toCredentialKey(clientCertificate.getPublicKeySha1());
        CredentialData credential = authRepository.getPublicKeyCredentials(domain.getId()).find(keyId);
        if (credential == null) {
            return null;
        }

        UserData user = authRepository.getUsers().find(credential.getUserId());
        if (user == null) {
            return null;
        }

        UserWithSecret userWithSecret = secretService.checkPublicKey(user, credential, clientCertificate, challenge,
                response);
        if (userWithSecret == null) {
            // TODO: Throttle?
            log.debug("Key mismatch for {}", user);
            return null;
        }

        return userWithSecret;
    }

    public static String toCredentialKey(ByteString publicKeySha1) {
        return BaseEncoding.base16().encode(publicKeySha1.toByteArray());
    }

    private AuthenticatedUser buildDomainToken(DomainData domain, UserWithSecret userWithSecret) throws CloudException {
        TokenScope scope = null;
        ProjectData project = null;
        ProjectRoles projectRoles = null;

        scope = TokenScope.Domain;

        return new AuthenticatedUser(scope, userWithSecret, project, projectRoles, domain);
    }

    private AuthenticatedUser buildProjectToken(DomainData domain, ProjectData project, UserWithSecret userWithSecret)
            throws CloudException {
        if (project == null) {
            throw new IllegalStateException();
        }

        TokenScope scope = TokenScope.Project;
        ProjectRoles projectRoles = null;

        UserData user = userWithSecret.getUserData();

        projectRoles = Users.findProjectRoles(user, project.getId());
        if (projectRoles == null) {
            return null;
        }

        return new AuthenticatedUser(scope, userWithSecret, project, projectRoles, domain);
    }

    private AuthenticatedUser buildProjectToken(DomainData domain, long projectId, UserWithSecret userWithSecret)
            throws CloudException {
        UserData user = userWithSecret.getUserData();

        ProjectRoles projectRoles = Users.findProjectRoles(user, projectId);
        if (projectRoles == null) {
            return null;
        }

        ProjectData project = authRepository.getProjects().find(projectRoles.getProject());
        if (project == null) {
            log.warn("Cannot find project {}", projectRoles.getProject());
            return null;
        }

        TokenScope scope = TokenScope.Project;
        return new AuthenticatedUser(scope, userWithSecret, project, projectRoles, domain);
    }

    @Override
    public ByteString createRegistrationChallenge(ClientCertificate clientCertificate) throws CloudException {
        AesKey secretKey = KeyczarUtils.generateSymmetricKey();
        byte[] payload = KeyczarUtils.pack(secretKey);
        byte[] plaintext = ChallengeResponses.addHeader(payload);

        // We can't encrypt because http proxies don't pass the public key :-(
        // It shouldn't add anything to security anyway
        // byte[] ciphertext = ChallengeResponses.encrypt(publicKey, plaintext);
        byte[] ciphertext = plaintext;

        ciphertext = ChallengeResponses.addHeader(ciphertext);

        return ByteString.copyFrom(ciphertext);
    }

}
TOP

Related Classes of io.fathom.cloud.identity.LoginServiceImpl$ProjectSpec

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.