package io.cattle.platform.iaas.api.auth.dao.impl; import static io.cattle.platform.core.model.tables.AccountTable.*; import static io.cattle.platform.core.model.tables.CredentialTable.*; import static io.cattle.platform.core.model.tables.ProjectMemberTable.*; import io.cattle.platform.api.auth.Identity; import io.cattle.platform.api.auth.Policy; import io.cattle.platform.archaius.util.ArchaiusUtil; import io.cattle.platform.core.constants.AccountConstants; import io.cattle.platform.core.constants.CommonStatesConstants; import io.cattle.platform.core.constants.CredentialConstants; import io.cattle.platform.core.constants.ProjectConstants; import io.cattle.platform.core.dao.AccountDao; import io.cattle.platform.core.dao.GenericResourceDao; import io.cattle.platform.core.model.Account; import io.cattle.platform.core.model.Credential; import io.cattle.platform.core.model.ProjectMember; import io.cattle.platform.core.model.tables.records.AccountRecord; import io.cattle.platform.core.model.tables.records.ProjectMemberRecord; import io.cattle.platform.db.jooq.dao.impl.AbstractJooqDao; import io.cattle.platform.iaas.api.auth.SecurityConstants; import io.cattle.platform.iaas.api.auth.dao.AuthDao; import io.cattle.platform.iaas.api.auth.projects.Member; import io.cattle.platform.iaas.api.auth.projects.ProjectLock; import io.cattle.platform.lock.LockCallback; import io.cattle.platform.lock.LockManager; import io.cattle.platform.object.ObjectManager; import io.cattle.platform.object.meta.ObjectMetaDataManager; import io.cattle.platform.object.process.ObjectProcessManager; import io.cattle.platform.object.process.StandardProcess; import io.cattle.platform.object.util.ObjectUtils; import io.github.ibuildthecloud.gdapi.exception.ClientVisibleException; import io.github.ibuildthecloud.gdapi.id.IdFormatter; import io.github.ibuildthecloud.gdapi.util.ResponseCodes; import io.github.ibuildthecloud.gdapi.util.TransformationService; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.inject.Inject; import org.apache.commons.lang3.StringUtils; import org.jooq.Condition; import org.jooq.Record; import org.jooq.SelectQuery; import org.jooq.TableField; import org.jooq.exception.InvalidResultException; import org.jooq.impl.DSL; import com.netflix.config.DynamicStringListProperty; public class AuthDaoImpl extends AbstractJooqDao implements AuthDao { @Inject GenericResourceDao resourceDao; @Inject ObjectManager objectManager; @Inject ObjectProcessManager objectProcessManager; @Inject LockManager lockManager; @Inject AccountDao accountDao; private DynamicStringListProperty SUPPORTED_TYPES = ArchaiusUtil.getList("account.by.key.credential.types"); @Override public Account getAdminAccount() { return create() .selectFrom(ACCOUNT) .where(ACCOUNT.STATE.in(getActiveStates()) .and(ACCOUNT.KIND.eq(AccountConstants.ADMIN_KIND))) .orderBy(ACCOUNT.ID.asc()).limit(1).fetchOne(); } @Override public List<Account> searchUsers(String username) { return create() .select(ACCOUNT.fields()) .from(ACCOUNT) .join(CREDENTIAL) .on(CREDENTIAL.ACCOUNT_ID.eq(ACCOUNT.ID)) .where(ACCOUNT.STATE.in(getActiveStates()) .and(CREDENTIAL.STATE.eq(CommonStatesConstants.ACTIVE)) .and(CREDENTIAL.PUBLIC_VALUE.contains(username)) .and(CREDENTIAL.KIND.eq(CredentialConstants.KIND_PASSWORD))) .orderBy(ACCOUNT.ID.asc()).fetchInto(Account.class); } @Override public Account getByUsername(String username) { try { return create() .select(ACCOUNT.fields()) .from(ACCOUNT) .join(CREDENTIAL) .on(CREDENTIAL.ACCOUNT_ID.eq(ACCOUNT.ID)) .where( ACCOUNT.STATE.in(getActiveStates()) .and(CREDENTIAL.STATE.eq(CommonStatesConstants.ACTIVE)) .and(CREDENTIAL.PUBLIC_VALUE.eq(username))) .and(CREDENTIAL.KIND.eq(CredentialConstants.KIND_PASSWORD)) .fetchOneInto(AccountRecord.class); } catch (InvalidResultException e) { throw new ClientVisibleException(ResponseCodes.CONFLICT, "MultipleOfUsername"); } } @Override public Account getAccountByLogin(String publicValue, String secretValue, TransformationService transformationService) { Credential credential = create() .selectFrom(CREDENTIAL) .where( CREDENTIAL.STATE.eq(CommonStatesConstants.ACTIVE)) .and(CREDENTIAL.PUBLIC_VALUE.eq(publicValue) .and(CREDENTIAL.KIND.equalIgnoreCase(CredentialConstants.KIND_PASSWORD))) .fetchOne(); if (credential == null) { return null; } boolean secretIsCorrect = transformationService.compare(secretValue, credential.getSecretValue()); if (secretIsCorrect) { return create() .selectFrom(ACCOUNT).where(ACCOUNT.ID.eq(credential.getAccountId()) .and(ACCOUNT.STATE.in(getActiveStates()))) .fetchOneInto(AccountRecord.class); } else { return null; } } @Override public String getRole(Account account, Policy policy, Policy authenticatedAsPolicy) { List<? extends ProjectMember> projectMembers; if (account != null && account.getKind().equalsIgnoreCase(ProjectConstants.TYPE)) { //check if authenticated as admin, if yes the role is assigned to be of "owner" boolean isAdmin = authenticatedAsPolicy.isOption(Policy.AUTHORIZED_FOR_ALL_ACCOUNTS); if (isAdmin) { return ProjectConstants.OWNER; } projectMembers = getProjectMembersByIdentity(account.getId(), policy.getIdentities()); if (projectMembers == null || projectMembers.size() == 0) { return account.getKind(); } else { String role = null; for (ProjectMember projectMember : projectMembers) { if (role == null) { role = projectMember.getRole(); } else { String newRole = projectMember.getRole(); if (getRolePriority(newRole) < getRolePriority(role)) { role = newRole; } } } return role; } } else if (account != null){ return account.getKind(); } else { return null; } } private int getRolePriority(String role) { return ArchaiusUtil.getInt(SecurityConstants.ROLE_SETTING_BASE + role).get(); } @Override public Account getAccountById(Long id) { return create() .selectFrom(ACCOUNT) .where( ACCOUNT.ID.eq(id) .and(ACCOUNT.STATE.ne(CommonStatesConstants.PURGED)) .and(ACCOUNT.REMOVED.isNull()) ).fetchOne(); } @Override public Account getAccountByAccessKey(String accessKey) { return create() .select(ACCOUNT.fields()) .from(ACCOUNT) .join(CREDENTIAL) .on(CREDENTIAL.ACCOUNT_ID.eq(ACCOUNT.ID)) .where( CREDENTIAL.STATE.eq(CommonStatesConstants.ACTIVE) .and(CREDENTIAL.PUBLIC_VALUE.eq(accessKey)) .and(CREDENTIAL.KIND.eq(CredentialConstants.KIND_API_KEY)) .and(ACCOUNT.STATE.in(getActiveStates()))) .fetchOneInto(AccountRecord.class); } @Override public Account getAccountByKeys(String access, String secretKey, TransformationService transformationService) { try { Credential credential = create() .selectFrom(CREDENTIAL) .where( CREDENTIAL.STATE.eq(CommonStatesConstants.ACTIVE)) .and(CREDENTIAL.PUBLIC_VALUE.eq(access)) .and(CREDENTIAL.KIND.in(SUPPORTED_TYPES.get())) .fetchOne(); if (credential == null) { return null; } if (transformationService.compare(secretKey, credential.getSecretValue())) { return create() .selectFrom(ACCOUNT).where(ACCOUNT.ID.eq(credential.getAccountId()) .and(ACCOUNT.STATE.in(getActiveStates()))) .fetchOneInto(AccountRecord.class); } else { return null; } } catch (InvalidResultException e) { throw new ClientVisibleException(ResponseCodes.CONFLICT, "MultipleKeys"); } } @Override public Account getAccountByExternalId(String externalId, String externalType) { return create() .selectFrom(ACCOUNT) .where( ACCOUNT.EXTERNAL_ID.eq(externalId) .and(ACCOUNT.EXTERNAL_ID_TYPE.eq(externalType)) .and(ACCOUNT.STATE.ne("purged")) ).orderBy(ACCOUNT.ID.asc()).fetchOne(); } @Override public Account createAccount(String name, String kind, String externalId, String externalType) { Account account = getAccountByExternalId(externalId, externalType); if (account != null){ throw new ClientVisibleException(ResponseCodes.UNAUTHORIZED); } Map<Object, Object> properties = new HashMap<>(); if (StringUtils.isNotEmpty(name)) { properties.put(ACCOUNT.NAME, name); } if (StringUtils.isNotEmpty(kind)) { properties.put(ACCOUNT.KIND, kind); } if (StringUtils.isNotEmpty(externalId)) { properties.put(ACCOUNT.EXTERNAL_ID, externalId); } if (StringUtils.isNotEmpty(externalType)) { properties.put(ACCOUNT.EXTERNAL_ID_TYPE, externalType); } return resourceDao.createAndSchedule(Account.class, objectManager.convertToPropertiesFor(Account.class, properties)); } @Override public Identity getIdentity(Long id, IdFormatter idFormatter) { Account account = getAccountById(id); if (account == null || account.getKind().equalsIgnoreCase(ProjectConstants.TYPE) || !accountDao.isActiveAccount(account)) { return null; } Credential credential = create() .selectFrom(CREDENTIAL) .where(CREDENTIAL.KIND.equalIgnoreCase(CredentialConstants.KIND_PASSWORD) .and(CREDENTIAL.ACCOUNT_ID.eq(id)) .and(CREDENTIAL.STATE.equalIgnoreCase(CommonStatesConstants.ACTIVE))).fetchAny(); String accountId = idFormatter != null ? (String) idFormatter.formatId(objectManager.getType(Account.class), account.getId()) : String.valueOf(id); return new Identity(ProjectConstants.RANCHER_ID, accountId, account.getName(), null, null, credential == null ? null : credential.getPublicValue()); } @Override public Account createProject(String name, String description) { Map<Object, Object> properties = new HashMap<>(); if (name != null) { properties.put(ACCOUNT.NAME, name); } if (description != null) { properties.put(ACCOUNT.DESCRIPTION, description); } properties.put(ACCOUNT.KIND, ProjectConstants.TYPE); return resourceDao.createAndSchedule(Account.class, objectManager.convertToPropertiesFor(Account.class, properties)); } protected List<String> getActiveStates() { return accountDao.getAccountActiveStates(); } @Override public Account getAccountByUuid(String uuid) { return create() .selectFrom(ACCOUNT) .where(ACCOUNT.UUID.eq(uuid) .and(ACCOUNT.STATE.in(getActiveStates()))) .orderBy(ACCOUNT.ID.asc()).limit(1).fetchOne(); } @Override public Account updateAccount(Account account, String name, String kind, String externalId, String externalType) { Map<TableField<AccountRecord, String>, String> properties = new HashMap<>(); if (StringUtils.isNotEmpty(name)) { properties.put(ACCOUNT.NAME, name); } if (StringUtils.isNotEmpty(kind)) { properties.put(ACCOUNT.KIND, kind); } if (StringUtils.isNotEmpty(externalId)) { properties.put(ACCOUNT.EXTERNAL_ID, externalId); } if (StringUtils.isNotEmpty(externalType)) { properties.put(ACCOUNT.EXTERNAL_ID_TYPE, externalType); } int updateCount = create() .update(ACCOUNT) .set(properties) .where(ACCOUNT.ID .eq(account.getId())) .execute(); if (1 != updateCount) { throw new RuntimeException("UpdateAccount failed."); } return objectManager.reload(account); } @Override public List<Account> getAccessibleProjects(Set<Identity> identities, boolean isAdmin, Long usingAccount) { List<Account> projects = new ArrayList<>(); if (identities == null) { return projects; } if (isAdmin) { projects.addAll(create() .selectFrom(ACCOUNT) .where(ACCOUNT.KIND.eq(ProjectConstants.TYPE) .and(ACCOUNT.REMOVED.isNull())) .orderBy(ACCOUNT.ID.asc()) .fetch()); return projects; } if (usingAccount != null) { Account project = getAccountById(usingAccount); if (project != null && project.getKind().equalsIgnoreCase(ProjectConstants.TYPE)) { projects.add(project); return projects; } } //DSL.falseCondition is created so that we can dynamically build a or //Condition without caring what the external Ids are and still make one //Database call. Condition allMembers = DSL.falseCondition(); for (Identity id : identities) { allMembers = allMembers.or(PROJECT_MEMBER.EXTERNAL_ID.eq(id.getExternalId()) .and(PROJECT_MEMBER.EXTERNAL_ID_TYPE.eq(id.getExternalIdType())) .and(PROJECT_MEMBER.REMOVED.isNull()) .and(PROJECT_MEMBER.STATE.eq(CommonStatesConstants.ACTIVE))); } SelectQuery<Record> query = create().selectQuery(); query.addFrom(ACCOUNT); query.addJoin(PROJECT_MEMBER, PROJECT_MEMBER.PROJECT_ID.equal(ACCOUNT.ID)); query.addConditions(allMembers); query.setDistinct(true); projects.addAll(query.fetchInto(ACCOUNT)); Map<Long, Account> returnProjects = new HashMap<>(); for (Account project : projects) { returnProjects.put(project.getId(), project); } projects = new ArrayList<>(); projects.addAll(returnProjects.values()); return projects; } @Override public List<? extends ProjectMember> getActiveProjectMembers(long id) { return create() .selectFrom(PROJECT_MEMBER) .where(PROJECT_MEMBER.PROJECT_ID.eq(id)) .and(PROJECT_MEMBER.STATE.eq(CommonStatesConstants.ACTIVE)) .and(PROJECT_MEMBER.REMOVED.isNull()) .orderBy(PROJECT_MEMBER.ID.asc()).fetch(); } @Override public List<? extends ProjectMember> getProjectMembersByIdentity(long projectId, Set<Identity> identities) { Condition allMembers = DSL.falseCondition(); for (Identity identity : identities) { allMembers = allMembers.or(PROJECT_MEMBER.EXTERNAL_ID.eq(identity.getExternalId()) .and(PROJECT_MEMBER.EXTERNAL_ID_TYPE.eq(identity.getExternalIdType())) .and(PROJECT_MEMBER.REMOVED.isNull()) .and(PROJECT_MEMBER.STATE.eq(CommonStatesConstants.ACTIVE)) .and(PROJECT_MEMBER.PROJECT_ID.eq(projectId))); } SelectQuery<Record> query = create().selectQuery(); query.addFrom(PROJECT_MEMBER); query.addConditions(allMembers); query.setDistinct(true); return query.fetchInto(PROJECT_MEMBER); } @Override public ProjectMember getProjectMember(long id) { return create() .selectFrom(PROJECT_MEMBER) .where(PROJECT_MEMBER.ID.eq(id)).fetchOne(); } @Override public boolean hasAccessToProject(long projectId, Long usingAccount, boolean isAdmin, Set<Identity> identities) { return isProjectMember(projectId, usingAccount, isAdmin, identities); } @Override public boolean isProjectOwner(long projectId, Long usingAccount, boolean isAdmin, Set<Identity> identities) { if (identities == null) { return false; } if (isAdmin) { return true; } if (usingAccount != null && usingAccount.equals(projectId)) { return false; } Set<ProjectMemberRecord> projectMembers = new HashSet<>(); Condition allMembers = DSL.falseCondition(); for (Identity id : identities) { allMembers = allMembers.or(PROJECT_MEMBER.EXTERNAL_ID.eq(id.getExternalId()) .and(PROJECT_MEMBER.EXTERNAL_ID_TYPE.eq(id.getExternalIdType())) .and(PROJECT_MEMBER.ROLE.eq(ProjectConstants.OWNER)) .and(PROJECT_MEMBER.PROJECT_ID.eq(projectId)) .and(PROJECT_MEMBER.STATE.eq(CommonStatesConstants.ACTIVE)) .and(PROJECT_MEMBER.REMOVED.isNull())); } projectMembers.addAll(create().selectFrom(PROJECT_MEMBER).where(allMembers).fetch()); return !projectMembers.isEmpty(); } @Override public boolean isProjectMember(long projectId, Long usingAccount, boolean isAdmin, Set<Identity> identities) { if (identities == null) { return false; } if ((usingAccount != null && usingAccount.equals(projectId)) || isAdmin) { return true; } Set<ProjectMemberRecord> projectMembers = new HashSet<>(); Condition allMembers = DSL.falseCondition(); for (Identity id : identities) { allMembers = allMembers.or(PROJECT_MEMBER.EXTERNAL_ID.eq(id.getExternalId()) .and(PROJECT_MEMBER.EXTERNAL_ID_TYPE.eq(id.getExternalIdType())) .and(PROJECT_MEMBER.PROJECT_ID.eq(projectId)) .and(PROJECT_MEMBER.STATE.eq(CommonStatesConstants.ACTIVE)) .and(PROJECT_MEMBER.REMOVED.isNull())); } projectMembers.addAll(create().selectFrom(PROJECT_MEMBER).where(allMembers).fetch()); return !projectMembers.isEmpty(); } @Override public List<? extends ProjectMember> setProjectMembers(final Account project, final Set<Member> members, final IdFormatter idFormatter) { return lockManager.lock(new ProjectLock(project), new LockCallback<List<? extends ProjectMember>>() { @Override public List<? extends ProjectMember> doWithLock() { List<? extends ProjectMember> previousMembers = getActiveProjectMembers(project.getId()); Set<Member> otherPreviousMembers = new HashSet<>(); for (ProjectMember member : previousMembers) { String projectId = (String) idFormatter.formatId(objectManager.getType(Account.class), member.getProjectId()); otherPreviousMembers.add(new Member(member, projectId)); } Set<Member> create = new HashSet<>(members); Set<Member> delete = new HashSet<>(otherPreviousMembers); for (Member member : members) { if (delete.remove(member)) { create.remove(member); } } Condition allMembers = DSL.falseCondition(); for (Member member : delete) { allMembers = allMembers.or(PROJECT_MEMBER.EXTERNAL_ID.eq(member.getExternalId()) .and(PROJECT_MEMBER.EXTERNAL_ID_TYPE.eq(member.getExternalIdType())) .and(PROJECT_MEMBER.PROJECT_ID.eq(project.getId())) .and(PROJECT_MEMBER.STATE.eq(CommonStatesConstants.ACTIVE))); } List<? extends ProjectMember> toDelete = create().selectFrom(PROJECT_MEMBER).where(allMembers).fetch(); for (ProjectMember member : toDelete) { objectProcessManager.executeStandardProcess(StandardProcess.DEACTIVATE, member, null); objectProcessManager.scheduleStandardProcess(StandardProcess.REMOVE, member, null); } for (Member member : create) { createProjectMember(project, member); } return getActiveProjectMembers(project.getId()); } }); } @Override public ProjectMember createProjectMember(Account project, Member member) { Map<Object, Object> properties = new HashMap<>(); properties.put(PROJECT_MEMBER.PROJECT_ID, project.getId()); properties.put(PROJECT_MEMBER.ACCOUNT_ID, project.getId()); properties.put(PROJECT_MEMBER.NAME, member.getName()); properties.put(PROJECT_MEMBER.EXTERNAL_ID, member.getExternalId()); properties.put(PROJECT_MEMBER.EXTERNAL_ID_TYPE, member.getExternalIdType()); properties.put(PROJECT_MEMBER.ROLE, member.getRole()); return resourceDao.create(ProjectMember.class, objectManager.convertToPropertiesFor(ProjectMember.class, properties)); } @Override public void ensureAllProjectsHaveNonRancherIdMembers(Identity identity) { //This operation is expensive if there are alot of projects and members however this is //only called when auth is being turned on. In most cases this will only be called once. Member newMember = new Member(identity, ProjectConstants.OWNER); Set<Identity> identities = new HashSet<>(); Account account = getAdminAccount(); identities.add(new Identity(ProjectConstants.RANCHER_ID, String.valueOf(account.getId()))); List<Account> allProjects = getAccessibleProjects(identities, true, null); for (Account project : allProjects) { List<? extends ProjectMember> members = getActiveProjectMembers(project.getId()); boolean hasNonRancherMember = false; for (ProjectMember member : members) { if (!member.getExternalIdType().equalsIgnoreCase(ProjectConstants.RANCHER_ID)) { hasNonRancherMember = true; } else if (member.getExternalId().equals(String.valueOf(getAdminAccount().getId()))) { deactivateThenRemove(member); } else { hasNonRancherMember = true; } } if (!hasNonRancherMember) { createProjectMember(project, newMember); } } } private void deactivateThenRemove(ProjectMember member) { Object state = ObjectUtils.getPropertyIgnoreErrors(member, ObjectMetaDataManager.STATE_FIELD); if (CommonStatesConstants.ACTIVE.equals(state)) { objectProcessManager.executeStandardProcess(StandardProcess.DEACTIVATE, member, null); member = objectManager.reload(member); } if (CommonStatesConstants.PURGED.equals(state)) { return; } objectProcessManager.executeStandardProcess(StandardProcess.REMOVE, member, null); } @Override public boolean hasAccessToAnyProject(Set<Identity> identities, boolean isAdmin, Long usingAccount) { return !getAccessibleProjects(identities, isAdmin, usingAccount).isEmpty(); } }