Package io.fathom.cloud.identity.services

Source Code of io.fathom.cloud.identity.services.IdentityServiceImpl

package io.fathom.cloud.identity.services;

import io.fathom.cloud.CloudException;
import io.fathom.cloud.WellKnownRoles;
import io.fathom.cloud.identity.LoginServiceImpl;
import io.fathom.cloud.identity.Users;
import io.fathom.cloud.identity.model.AuthenticatedProject;
import io.fathom.cloud.identity.model.AuthenticatedUser;
import io.fathom.cloud.identity.secrets.Migrations;
import io.fathom.cloud.identity.secrets.SecretToken;
import io.fathom.cloud.identity.secrets.SecretToken.SecretTokenType;
import io.fathom.cloud.identity.secrets.Secrets;
import io.fathom.cloud.identity.state.AuthRepository;
import io.fathom.cloud.openstack.client.identity.ChallengeResponses;
import io.fathom.cloud.protobuf.IdentityModel.CredentialData;
import io.fathom.cloud.protobuf.IdentityModel.DomainData;
import io.fathom.cloud.protobuf.IdentityModel.DomainRoles;
import io.fathom.cloud.protobuf.IdentityModel.GroupData;
import io.fathom.cloud.protobuf.IdentityModel.ProjectData;
import io.fathom.cloud.protobuf.IdentityModel.ProjectRoles;
import io.fathom.cloud.protobuf.IdentityModel.RoleData;
import io.fathom.cloud.protobuf.IdentityModel.UserData;
import io.fathom.cloud.protobuf.IdentityModel.UserSecretData;
import io.fathom.cloud.state.DuplicateValueException;
import io.fathom.cloud.state.NamedItemCollection;
import io.fathom.cloud.tasks.TaskScheduler;

import java.util.List;
import java.util.Set;

import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response.Status;

import org.keyczar.AesKey;
import org.keyczar.DefaultKeyType;
import org.keyczar.KeyMetadata;
import org.keyczar.KeyczarKey;
import org.keyczar.KeyczarUtils;
import org.keyczar.enums.KeyPurpose;
import org.keyczar.exceptions.KeyczarException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.inject.persist.Transactional;

@Singleton
@Transactional
public class IdentityServiceImpl implements IdentityService {
    private static final Logger log = LoggerFactory.getLogger(IdentityServiceImpl.class);

    @Inject
    AuthRepository authRepository;

    @Inject
    Secrets secretService;

    @Inject
    TaskScheduler scheduler;

    // @Override
    // public void deleteUser(long id) throws StateStoreException,
    // ConcurrentUpdateException {
    //
    // // TODO: Mark as deleted?
    // // TODO: Deleted related things e.g. credentials?
    // // TODO: Block delete if "in use"
    // authStore.getUsers().delete(id);
    // }

    @Override
    @Transactional
    public UserData createUser(UserCreationData request) throws CloudException {
        UserData.Builder user = request.user;
        request.user = null;

        String username = user.getName();
        if (Strings.isNullOrEmpty(username)) {
            throw new IllegalArgumentException();
        }

        user.setDomainId(request.domain.getId());

        String challengeKey = null;
        if (request.publicKeySha1 != null) {
            challengeKey = LoginServiceImpl.toCredentialKey(request.publicKeySha1);
        }

        if (Strings.isNullOrEmpty(request.password)) {
            request.password = null;
        }

        NamedItemCollection<CredentialData> usernameStore = authRepository.getUsernames(request.domain);

        CredentialData credential = usernameStore.find(username);
        if (credential != null) {
            throw new WebApplicationException(Status.CONFLICT);
        }

        if (challengeKey != null) {
            CredentialData publicKeyCredential = authRepository.getPublicKeyCredentials(request.domain.getId()).find(
                    challengeKey);
            if (publicKeyCredential != null) {
                throw new WebApplicationException(Status.CONFLICT);
            }
        }

        if (challengeKey == null && request.password == null) {
            // There's going to be no way to log in
            throw new IllegalArgumentException();
        }

        SecretToken userSecret;

        if (request.publicKeyChallengeRequest == null) {
            userSecret = SecretToken.create(SecretTokenType.USER_SECRET);
        } else {
            byte[] plaintext = request.publicKeyChallengeRequest.toByteArray();
            if (!ChallengeResponses.hasPrefix(plaintext)) {
                throw new IllegalArgumentException();
            }
            byte[] payload = ChallengeResponses.getPayload(plaintext);
            payload = ChallengeResponses.getPayload(payload);
            AesKey cryptoKey;
            try {
                cryptoKey = KeyczarUtils.unpack(payload);
            } catch (KeyczarException e) {
                throw new IllegalArgumentException("Invalid key", e);
            }
            userSecret = new SecretToken(SecretTokenType.USER_SECRET, cryptoKey, null);
        }

        if (request.password != null) {
            secretService.addPasswordAuth(user, userSecret, request.password);
        }

        // TODO: Allow users to opt out of password/token recovery??
        boolean allowPasswordRecovery = true;
        if (allowPasswordRecovery) {
            secretService.addTokenRecovery(user, userSecret);
        }

        if (challengeKey != null) {
            secretService.addPublicKeyAuth(user, challengeKey, request.publicKeyChallengeResponse);
        }

        KeyczarKey keypair = KeyczarUtils.createKey(new KeyMetadata("RSA Key", KeyPurpose.DECRYPT_AND_ENCRYPT,
                DefaultKeyType.RSA_PRIV));

        user.getPublicKeyBuilder().setKeyczar(KeyczarUtils.getPublicKey(keypair).toString());

        // Create the secret data
        {
            if (user.hasSecretData()) {
                throw new IllegalStateException();
            }

            UserSecretData.Builder s = UserSecretData.newBuilder();
            if (Strings.isNullOrEmpty(user.getName())) {
                throw new IllegalArgumentException();
            }
            // s.setVerifyPublicKey(Hashing.md5().hashBytes(publicKey).asLong());
            s.getPrivateKeyBuilder().setKeyczar(keypair.toString());

            user.setSecretData(Secrets.buildUserSecret(userSecret, s.build()));
        }

        UserData created = authRepository.getUsers().create(user);

        {
            CredentialData.Builder credentialBuilder = CredentialData.newBuilder();

            credentialBuilder.setUserId(created.getId());
            credentialBuilder.setKey(username);

            // if (!Strings.isNullOrEmpty(password)) {
            // PasswordHashData passwordHash = hasher.hash(password);
            // credentialBuilder.setPasswordHash(passwordHash);
            // }

            try {
                usernameStore.create(credentialBuilder);
            } catch (DuplicateValueException e) {
                // TODO: We need to be atomic! ZK supports multi, but it looks
                // complicated
                throw new WebApplicationException(Status.CONFLICT);
            }
        }

        if (challengeKey != null) {
            CredentialData.Builder b = CredentialData.newBuilder();

            b.setUserId(created.getId());
            b.setKey(challengeKey);

            try {
                authRepository.getPublicKeyCredentials(request.domain.getId()).create(b);
            } catch (DuplicateValueException e) {
                // TODO: We need to be atomic! ZK supports multi, but it looks
                // complicated
                throw new WebApplicationException(Status.CONFLICT);
            }
        }

        return created;

    }

    @Override
    public DomainData getDefaultDomain() throws CloudException {
        return authRepository.getDomains().getDefaultDomain();
    }

    @Override
    public ProjectData createProject(ProjectData.Builder b, AuthenticatedUser owner, long ownerRoleId)
            throws CloudException {
        // TODO: Policy that only domain admins can create projects?
        // Don't see why!

        // Auth.Domain domain = findDomainWithAdminRole();
        // if (domain == null) {
        // throw new WebApplicationException(Status.FORBIDDEN);
        // }

        ProjectData created = authRepository.getProjects().create(b);

        SecretToken secretToken = SecretToken.create(SecretTokenType.PROJECT_SECRET);

        AuthenticatedProject authenticatedProject = new AuthenticatedProject(created, secretToken);

        grantRoleToUserOnProject(authenticatedProject, owner.getUserId(), ownerRoleId);

        return created;
    }

    @Override
    @Transactional
    public void grantRoleToUserOnProject(AuthenticatedProject authenticatedProject, long granteeUserId, long roleId)
            throws CloudException {
        RoleData role = authRepository.getRoles().find(roleId);
        if (role == null) {
            throw new IllegalArgumentException("Cannot find role");
        }

        long projectId = authenticatedProject.getProjectId();

        UserData granteeData = authRepository.getUsers().find(granteeUserId);
        if (granteeData == null) {
            throw new IllegalArgumentException();
        }

        UserData.Builder b = UserData.newBuilder(granteeData);

        {
            ProjectRoles.Builder pb = null;
            for (ProjectRoles.Builder i : b.getProjectRolesBuilderList()) {
                if (i.getProject() == projectId) {
                    pb = i;
                    break;
                }
            }

            if (pb == null) {
                pb = b.addProjectRolesBuilder();
                pb.setProject(projectId);
            }

            if (!pb.hasSecretData()) {
                try {
                    pb.setSecretData(Secrets.buildProjectRolesSecret(granteeData, authenticatedProject));
                } catch (KeyczarException e) {
                    throw new CloudException("Crypto error granting project role", e);
                }
            }

            if (!pb.getRoleList().contains(role.getId())) {
                pb.addRole(role.getId());
            }

            authRepository.getUsers().update(b);
        }
    }

    @Override
    @Transactional
    public void grantDomainRoleToUser(long domainId, long granteeUserId, long roleId) throws CloudException {
        RoleData role = authRepository.getRoles().find(roleId);
        if (role == null) {
            throw new IllegalArgumentException("Cannot find role");
        }

        UserData granteeData = authRepository.getUsers().find(granteeUserId);
        if (granteeData == null) {
            throw new IllegalArgumentException();
        }

        DomainData domain = authRepository.getDomains().find(domainId);
        if (domain == null) {
            throw new IllegalArgumentException();
        }

        UserData.Builder b = UserData.newBuilder(granteeData);

        {
            DomainRoles.Builder rb = null;
            for (DomainRoles.Builder i : b.getDomainRolesBuilderList()) {
                if (i.getDomain() == domainId) {
                    rb = i;
                    break;
                }
            }

            if (rb == null) {
                rb = b.addDomainRolesBuilder();
                rb.setDomain(domainId);
            }

            if (!rb.getRoleList().contains(role.getId())) {
                rb.addRole(role.getId());
            }

            authRepository.getUsers().update(b);
        }
    }

    @Override
    public ProjectData findProject(AuthenticatedUser user, long projectId) throws CloudException {
        ProjectData project = authRepository.getProjects().find(projectId);
        boolean authorized = false;

        if (project != null) {
            ProjectRoles projectRoles = Users.findProjectRoles(user.getUserData(), project.getId());
            if (projectRoles != null && projectRoles.getRoleCount() != 0) {
                authorized = true;
            }

            if (!authorized) {
                if (user.isDomainAdmin(project.getDomainId())) {
                    authorized = true;
                }
            }
        }

        if (!authorized) {
            log.info("User {} not authorized on project {}", user, project);
            project = null;
        }

        return project;
    }

    @Override
    public void deleteUser(UserData user) throws CloudException {
        // TODO: Mark as deleted?
        // TODO: Deleted related things e.g. credentials?
        // TODO: Block delete if "in use"
        authRepository.getUsers().delete(user.getId());
    }

    @Inject
    Provider<Sweeper> sweeper;

    @Override
    public void sweep() throws CloudException {
        sweeper.get().sweep();
    }

    @Override
    public RoleData findRole(long roleId) {
        return authRepository.getRoles().find(roleId);
    }

    @Override
    public List<RoleData> listRoles() {
        return authRepository.getRoles().list();
    }

    @Override
    public void fixupProject(AuthenticatedUser user, long projectId) throws CloudException {
        ProjectData project = findProject(user, projectId);
        if (project == null) {
            log.warn("Could not find project");
            return;
        }

        ProjectRoles projectRoles = Users.findProjectRoles(user.getUserData(), project.getId());
        if (projectRoles == null) {
            log.warn("Could not find role on project");
            // TODO: We probably need another path for domain admins
            return;
        }

        if (!projectRoles.hasSecretData()) {
            log.warn("Project role has no secret data");

            if (projectRoles.getRoleList().contains(WellKnownRoles.ROLE_ID_ADMIN)) {
                // TODO: Remove once we've migrated all the projects
                log.warn("Creating project key for project: {}", projectId);

                Migrations.report(project);

                AesKey cryptoKey = KeyczarUtils.generateSymmetricKey();

                long userId = user.getUserId();

                SecretToken secretToken = new SecretToken(SecretTokenType.PROJECT_SECRET, cryptoKey, null);
                AuthenticatedProject authenticatedProject = new AuthenticatedProject(project, secretToken);

                grantRoleToUserOnProject(authenticatedProject, userId, WellKnownRoles.ROLE_ID_ADMIN);
            } else {
                log.warn("User is not admin, cannot create secret");
            }
        }
    }

    @Override
    public AuthenticatedProject authenticateToProject(AuthenticatedUser user, long projectId) throws CloudException {
        ProjectData project = findProject(user, projectId);
        if (project == null) {
            return null;
        }

        AuthenticatedProject authenticatedProject = secretService.authenticate(project, user);
        if (authenticatedProject == null) {
            return null;
        }

        return authenticatedProject;
    }

    @Override
    public UserData findUser(long userId) throws CloudException {
        return authRepository.getUsers().find(userId);
    }

    @Override
    public void start() throws CloudException {
        // TODO: Just support method annotations??
        scheduler.schedule(SweepTask.class);
    }

    @Override
    public List<DomainData> listDomains(UserData user) throws CloudException {
        List<DomainData> ret = Lists.newArrayList();
        for (DomainData domain : authRepository.getDomains().list()) {
            // TODO: Other domains?
            if (domain.getId() != user.getDomainId()) {
                continue;
            }
            ret.add(domain);
        }
        return ret;
    }

    @Override
    public DomainData findDomain(UserData user, String id) throws CloudException {
        DomainData domain = authRepository.getDomains().find(Long.valueOf(id));

        // TODO: Other domains?
        if (domain != null && domain.getId() != user.getDomainId()) {
            domain = null;
        }

        return domain;
    }

    @Override
    public List<GroupData> listGroups(AuthenticatedUser user) throws CloudException {
        long domainId = user.getDomainId();
        boolean isAdmin = user.isDomainAdmin(domainId);

        Set<Long> userGroups = Sets.newHashSet(user.getUserData().getGroupsList());

        List<GroupData> ret = Lists.newArrayList();
        for (GroupData group : authRepository.getGroups(domainId).list()) {
            if (!isAdmin) {
                if (!userGroups.contains(group.getId())) {
                    continue;
                }
            }
            ret.add(group);
        }

        return ret;
    }

    @Override
    public UserData findUserByName(long domainId, String userName) throws CloudException {
        DomainData domain = authRepository.getDomains().find(domainId);
        if (domain == null) {
            throw new IllegalArgumentException();
        }

        CredentialData credentialData = authRepository.getUsernames(domain).find(userName);
        if (credentialData == null) {
            return null;
        }

        long userId = credentialData.getUserId();
        UserData user = authRepository.getUsers().find(userId);

        if (user == null) {
            // Unexpected!
            log.warn("Unable to find user for credential: {}", userName);
        }

        return user;

    }

    @Override
    public List<ProjectData> listProjects() throws CloudException {
        return authRepository.getProjects().list();
    }
}
TOP

Related Classes of io.fathom.cloud.identity.services.IdentityServiceImpl

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.