All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openmetadata.service.jdbi3.UserRepository Maven / Gradle / Ivy

There is a newer version: 1.5.11
Show newest version
/*
 *  Copyright 2021 Collate
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *  http://www.apache.org/licenses/LICENSE-2.0
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.openmetadata.service.jdbi3;

import static org.openmetadata.common.utils.CommonUtil.listOrEmpty;
import static org.openmetadata.common.utils.CommonUtil.listOrEmptyMutable;
import static org.openmetadata.common.utils.CommonUtil.nullOrEmpty;
import static org.openmetadata.csv.CsvUtil.addEntityReferences;
import static org.openmetadata.csv.CsvUtil.addField;
import static org.openmetadata.schema.type.Include.ALL;
import static org.openmetadata.schema.type.Include.NON_DELETED;
import static org.openmetadata.schema.utils.EntityInterfaceUtil.quoteName;
import static org.openmetadata.service.Entity.FIELD_DOMAINS;
import static org.openmetadata.service.Entity.ROLE;
import static org.openmetadata.service.Entity.TEAM;
import static org.openmetadata.service.Entity.USER;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
import java.util.stream.Collectors;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.core.UriInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.csv.CSVPrinter;
import org.apache.commons.csv.CSVRecord;
import org.jdbi.v3.sqlobject.transaction.Transaction;
import org.openmetadata.csv.EntityCsv;
import org.openmetadata.schema.api.teams.CreateTeam.TeamType;
import org.openmetadata.schema.api.teams.CreateUser;
import org.openmetadata.schema.entity.teams.AuthenticationMechanism;
import org.openmetadata.schema.entity.teams.Team;
import org.openmetadata.schema.entity.teams.User;
import org.openmetadata.schema.services.connections.metadata.AuthProvider;
import org.openmetadata.schema.type.EntityReference;
import org.openmetadata.schema.type.Include;
import org.openmetadata.schema.type.Relationship;
import org.openmetadata.schema.type.csv.CsvDocumentation;
import org.openmetadata.schema.type.csv.CsvErrorType;
import org.openmetadata.schema.type.csv.CsvFile;
import org.openmetadata.schema.type.csv.CsvHeader;
import org.openmetadata.schema.type.csv.CsvImportResult;
import org.openmetadata.schema.utils.EntityInterfaceUtil;
import org.openmetadata.service.Entity;
import org.openmetadata.service.OpenMetadataApplicationConfig;
import org.openmetadata.service.exception.BadRequestException;
import org.openmetadata.service.exception.CatalogExceptionMessage;
import org.openmetadata.service.exception.EntityNotFoundException;
import org.openmetadata.service.jdbi3.CollectionDAO.EntityRelationshipRecord;
import org.openmetadata.service.resources.teams.UserResource;
import org.openmetadata.service.secrets.SecretsManager;
import org.openmetadata.service.secrets.SecretsManagerFactory;
import org.openmetadata.service.security.SecurityUtil;
import org.openmetadata.service.security.auth.BotTokenCache;
import org.openmetadata.service.security.policyevaluator.SubjectContext;
import org.openmetadata.service.util.EntityUtil;
import org.openmetadata.service.util.EntityUtil.Fields;
import org.openmetadata.service.util.JsonUtils;
import org.openmetadata.service.util.UserUtil;

@Slf4j
public class UserRepository extends EntityRepository {
  static final String ROLES_FIELD = "roles";
  static final String TEAMS_FIELD = "teams";
  public static final String AUTH_MECHANISM_FIELD = "authenticationMechanism";
  static final String USER_PATCH_FIELDS =
      "profile,roles,teams,authenticationMechanism,isEmailVerified,personas,defaultPersona,domains";
  static final String USER_UPDATE_FIELDS =
      "profile,roles,teams,authenticationMechanism,isEmailVerified,personas,defaultPersona,domains";
  private volatile EntityReference organization;

  public UserRepository() {
    super(
        UserResource.COLLECTION_PATH,
        USER,
        User.class,
        Entity.getCollectionDAO().userDAO(),
        USER_PATCH_FIELDS,
        USER_UPDATE_FIELDS);
    this.quoteFqn = true;
    supportsSearch = true;
  }

  private EntityReference getOrganization() {
    if (organization == null) {
      organization = Entity.getEntityReferenceByName(TEAM, Entity.ORGANIZATION_NAME, Include.ALL);
    }
    return organization;
  }

  // with the introduction of fqnHash we added case sensitivity to all the entities
  // however usernames , emails cannot be case-sensitive
  @Override
  public void setFullyQualifiedName(User user) {
    user.setFullyQualifiedName(quoteName(user.getName().toLowerCase()));
  }

  public final Fields getFieldsWithUserAuth(String fields) {
    Set tempFields = getAllowedFieldsCopy();
    if (fields != null && fields.equals("*")) {
      tempFields.add(AUTH_MECHANISM_FIELD);
      return new Fields(tempFields);
    }
    return new Fields(tempFields, fields);
  }

  @Override
  public User getByName(UriInfo uriInfo, String name, Fields fields) {
    return super.getByName(uriInfo, EntityInterfaceUtil.quoteName(name), fields);
  }

  private List getDomains(UUID teamId) {
    // Team does not have domain. 'domains' is the field for user as team can belong to multiple
    // domains
    return findFrom(teamId, USER, Relationship.HAS, Entity.DOMAIN);
  }

  @Override
  protected void storeDomain(User entity, EntityReference exclude) {
    for (EntityReference domainRef : listOrEmpty(entity.getDomains())) {
      // Add relationship domain --- has ---> entity
      LOG.info(
          "Adding domain {} for user {}:{}",
          domainRef.getFullyQualifiedName(),
          entityType,
          entity.getId());
      addRelationship(
          domainRef.getId(), entity.getId(), Entity.DOMAIN, entityType, Relationship.HAS);
    }
  }

  public User getByEmail(UriInfo uriInfo, String email, Fields fields) {
    String userString = daoCollection.userDAO().findUserByEmail(email);
    if (userString == null) {
      throw EntityNotFoundException.byMessage(CatalogExceptionMessage.entityNotFound(USER, email));
    }
    User user = JsonUtils.readValue(userString, User.class);
    setFieldsInternal(user, fields);
    setInheritedFields(user, fields);
    // Clone the entity
    User entityClone = JsonUtils.deepCopy(user, User.class);
    clearFieldsInternal(entityClone, fields);
    return withHref(uriInfo, entityClone);
  }

  public User getUserByNameAndEmail(UriInfo uriInfo, String name, String email, Fields fields) {
    String userString = daoCollection.userDAO().findUserByNameAndEmail(name, email);
    if (userString == null) {
      throw EntityNotFoundException.byMessage(CatalogExceptionMessage.entityNotFound(USER, email));
    }
    User user = JsonUtils.readValue(userString, User.class);
    setFieldsInternal(user, fields);
    setInheritedFields(user, fields);
    // Clone the entity
    User entityClone = JsonUtils.deepCopy(user, User.class);
    clearFieldsInternal(entityClone, fields);
    return withHref(uriInfo, entityClone);
  }

  /** Ensures that the default roles are added for POST, PUT and PATCH operations. */
  @Override
  public void prepare(User user, boolean update) {
    validateTeams(user);
    validateRoles(user.getRoles());
  }

  @Override
  public void restorePatchAttributes(User original, User updated) {
    // Patch can't make changes to following fields. Ignore the changes
    super.restorePatchAttributes(original, updated);
    updated
        .withInheritedRoles(original.getInheritedRoles())
        .withAuthenticationMechanism(original.getAuthenticationMechanism());
  }

  private List getInheritedRoles(User user) {
    if (Boolean.TRUE.equals(user.getIsBot())) {
      return Collections.emptyList(); // No inherited roles for bots
    }
    return SubjectContext.getRolesForTeams(getTeams(user));
  }

  @Override
  public void storeEntity(User user, boolean update) {
    // Relationships and fields such as href are derived and not stored as part of json
    List roles = user.getRoles();
    List teams = user.getTeams();

    // Don't store roles, teams and href as JSON. Build it on the fly based on relationships
    user.withRoles(null).withTeams(null).withInheritedRoles(null);

    SecretsManager secretsManager = SecretsManagerFactory.getSecretsManager();
    if (secretsManager != null && Boolean.TRUE.equals(user.getIsBot())) {
      secretsManager.encryptAuthenticationMechanism(
          user.getName(), user.getAuthenticationMechanism());
    }

    store(user, update);

    // Restore the relationships
    user.withRoles(roles).withTeams(teams);
  }

  @Override
  public void storeRelationships(User user) {
    assignRoles(user, user.getRoles());
    assignTeams(user, user.getTeams());
    assignDefaultPersona(user, user.getDefaultPersona());
    assignPersonas(user, user.getPersonas());
    user.setInheritedRoles(getInheritedRoles(user));
  }

  @Override
  public void setInheritedFields(User user, Fields fields) {
    // If user does not have domain, then inherit it from parent Team
    // TODO have default team when a user belongs to multiple teams
    if (fields.contains(FIELD_DOMAINS)) {
      Set combinedParent = new TreeSet<>(EntityUtil.compareEntityReferenceById);
      List teams =
          !fields.contains(TEAMS_FIELD) ? getTeams(user) : user.getTeams();
      if (!nullOrEmpty(teams)) {
        for (EntityReference team : teams) {
          Team parent = Entity.getEntity(TEAM, team.getId(), "domains", ALL);
          combinedParent.addAll(parent.getDomains());
        }
      }
      user.setDomains(
          EntityUtil.mergedInheritedEntityRefs(
              user.getDomains(), combinedParent.stream().toList()));
    }
  }

  @Override
  public UserUpdater getUpdater(User original, User updated, Operation operation) {
    return new UserUpdater(original, updated, operation);
  }

  @Override
  public void setFields(User user, Fields fields) {
    user.setTeams(fields.contains(TEAMS_FIELD) ? getTeams(user) : user.getTeams());
    user.setOwns(fields.contains("owns") ? getOwns(user) : user.getOwns());
    user.setFollows(fields.contains("follows") ? getFollows(user) : user.getFollows());
    user.setRoles(fields.contains(ROLES_FIELD) ? getRoles(user) : user.getRoles());
    user.setPersonas(fields.contains("personas") ? getPersonas(user) : user.getPersonas());
    user.setDefaultPersona(
        fields.contains("defaultPersonas") ? getDefaultPersona(user) : user.getDefaultPersona());
    user.withInheritedRoles(
        fields.contains(ROLES_FIELD) ? getInheritedRoles(user) : user.getInheritedRoles());
    user.setDomains(fields.contains("domains") ? getDomains(user.getId()) : user.getDomains());
  }

  @Override
  public void clearFields(User user, Fields fields) {
    user.setProfile(fields.contains("profile") ? user.getProfile() : null);
    user.setTeams(fields.contains(TEAMS_FIELD) ? user.getTeams() : null);
    user.setOwns(fields.contains("owns") ? user.getOwns() : null);
    user.setFollows(fields.contains("follows") ? user.getFollows() : null);
    user.setRoles(fields.contains(ROLES_FIELD) ? user.getRoles() : null);
    user.setAuthenticationMechanism(
        fields.contains(AUTH_MECHANISM_FIELD) ? user.getAuthenticationMechanism() : null);
    user.withInheritedRoles(fields.contains(ROLES_FIELD) ? user.getInheritedRoles() : null);
  }

  @Override
  public String exportToCsv(String importingTeam, String user) throws IOException {
    Team team = daoCollection.teamDAO().findEntityByName(importingTeam);
    return new UserCsv(team, user).exportCsv();
  }

  @Override
  public CsvImportResult importFromCsv(
      String importingTeam, String csv, boolean dryRun, String user) throws IOException {
    Team team = daoCollection.teamDAO().findEntityByName(importingTeam);
    UserCsv userCsv = new UserCsv(team, user);
    return userCsv.importCsv(csv, dryRun);
  }

  public boolean isTeamJoinable(String teamId) {
    Team team =
        daoCollection.teamDAO().findEntityById(UUID.fromString(teamId), Include.NON_DELETED);
    return team.getIsJoinable();
  }

  public void validateTeams(User user) {
    List teams = user.getTeams();
    if (teams != null) {
      for (EntityReference entityReference : teams) {
        EntityReference ref =
            Entity.getEntityReferenceById(Entity.TEAM, entityReference.getId(), ALL);
        EntityUtil.copy(ref, entityReference);
      }
      teams.sort(EntityUtil.compareEntityReference);
    } else {
      user.setTeams(new ArrayList<>(List.of(getOrganization()))); // Organization is a default team
    }
  }

  /* Validate if the user is already part of the given team */
  public void validateTeamAddition(UUID userId, UUID teamId) {
    User user = find(userId, NON_DELETED);
    List teams = getTeams(user);
    Optional team =
        teams.stream().filter(t -> t.getId().equals(teamId)).findFirst();
    if (team.isPresent()) {
      throw new IllegalArgumentException(
          CatalogExceptionMessage.userAlreadyPartOfTeam(
              user.getName(), team.get().getDisplayName()));
    }
  }

  public boolean checkEmailAlreadyExists(String emailId) {
    return daoCollection.userDAO().checkEmailExists(emailId) > 0;
  }

  public boolean checkUserNameExists(String username) {
    return daoCollection.userDAO().checkUserNameExists(username) > 0;
  }

  public void initializeUsers(OpenMetadataApplicationConfig config) {
    AuthProvider authProvider = config.getAuthenticationConfiguration().getProvider();
    // Create Admins
    Set adminUsers =
        new HashSet<>(config.getAuthorizerConfiguration().getAdminPrincipals());
    String domain = SecurityUtil.getDomain(config);
    UserUtil.addUsers(authProvider, adminUsers, domain, true);

    // Create Test Users
    Set testUsers = new HashSet<>(config.getAuthorizerConfiguration().getTestPrincipals());
    UserUtil.addUsers(authProvider, testUsers, domain, null);
  }

  private List getOwns(User user) {
    // Compile entities owned by the user
    List ownedEntities =
        daoCollection.relationshipDAO().findTo(user.getId(), USER, Relationship.OWNS.ordinal());

    // Compile entities owned by the team the user belongs to
    List teams = user.getTeams() == null ? getTeams(user) : user.getTeams();
    for (EntityReference team : teams) {
      ownedEntities.addAll(
          daoCollection
              .relationshipDAO()
              .findTo(team.getId(), Entity.TEAM, Relationship.OWNS.ordinal()));
    }
    // Populate details in entity reference
    return EntityUtil.getEntityReferences(ownedEntities);
  }

  private List getFollows(User user) {
    return findTo(user.getId(), USER, Relationship.FOLLOWS, null);
  }

  private List getTeamChildren(UUID teamId) {
    if (teamId.equals(
        getOrganization().getId())) { // For organization all the parentless teams are children
      List children = daoCollection.teamDAO().listTeamsUnderOrganization(teamId);
      return EntityUtil.populateEntityReferencesById(EntityUtil.strToIds(children), Entity.TEAM);
    }
    return findTo(teamId, TEAM, Relationship.PARENT_OF, TEAM);
  }

  public List getGroupTeams(
      UriInfo uriInfo, SecurityContext context, String email) {
    // Cleanup
    User user =
        getLoggedInUserByNameAndEmail(
            uriInfo, context.getUserPrincipal().getName(), email, Fields.EMPTY_FIELDS);
    List teams = getTeams(user);
    return getGroupTeams(teams);
  }

  public User getLoggedInUserByNameAndEmail(
      UriInfo uriInfo, String username, String email, Fields fields) {
    try {
      return getUserByNameAndEmail(uriInfo, username, email, fields);
    } catch (EntityNotFoundException e) {
      boolean existByName = checkUserNameExists(username);
      boolean existByEmail = checkEmailAlreadyExists(email);
      if (existByName && !existByEmail) {
        User userByName = getByName(uriInfo, username, Fields.EMPTY_FIELDS);
        throw BadRequestException.of(
            String.format(
                "User with given name exists but is not associated with the provided email. "
                    + "Matching User Found By Name [username:email] : [%s:%s], Provided User: [%s:%s]",
                userByName.getName().toLowerCase(),
                userByName.getEmail().toLowerCase(),
                username,
                email));
      } else if (!existByName && existByEmail) {
        User userByEmail = getByEmail(uriInfo, email, Fields.EMPTY_FIELDS);
        throw BadRequestException.of(
            String.format(
                "User with given email exists but is not associated with provider username. "
                    + "Matching User Found By Email [username:email] : [%s:%s], Provided User: [%s:%s]",
                userByEmail.getName().toLowerCase(),
                userByEmail.getEmail().toLowerCase(),
                username,
                email));
      } else {
        throw EntityNotFoundException.byMessage(
            String.format(
                "User with provider name : %s and email : %s not found", username, email));
      }
    }
  }

  private List getGroupTeams(List teams) {
    Set result = new HashSet<>();
    for (EntityReference t : teams) {
      Team team = Entity.getEntity(t, "", Include.ALL);
      if (TeamType.GROUP.equals(team.getTeamType())) {
        result.add(t);
      } else {
        List children = getTeamChildren(team.getId());
        result.addAll(getGroupTeams(children));
      }
    }
    return new ArrayList<>(result);
  }

  /* Get all the roles that user has been assigned and inherited from the team to User entity */
  private List getRoles(User user) {
    return findTo(user.getId(), USER, Relationship.HAS, Entity.ROLE);
  }

  /* Get all the teams that user belongs to User entity */
  public List getTeams(User user) {
    List teams = findFrom(user.getId(), USER, Relationship.HAS, Entity.TEAM);
    // Filter deleted teams
    teams =
        listOrEmpty(teams).stream().filter(team -> !team.getDeleted()).collect(Collectors.toList());
    // If there are no teams that a user belongs to then return organization as the default team
    if (listOrEmpty(teams).isEmpty()) {
      return new ArrayList<>(List.of(getOrganization()));
    }
    return teams;
  }

  public List getPersonas(User user) {
    return findFrom(user.getId(), USER, Relationship.APPLIED_TO, Entity.PERSONA);
  }

  public EntityReference getDefaultPersona(User user) {
    return getToEntityRef(user.getId(), Relationship.DEFAULTS_TO, Entity.PERSONA, false);
  }

  private void assignRoles(User user, List roles) {
    roles = listOrEmpty(roles);
    for (EntityReference role : roles) {
      addRelationship(user.getId(), role.getId(), USER, Entity.ROLE, Relationship.HAS);
    }
  }

  private void assignTeams(User user, List teams) {
    teams = listOrEmpty(teams);
    for (EntityReference team : teams) {
      if (team.getId().equals(getOrganization().getId())) {
        continue; // Default relationship user to organization team is not stored
      }
      addRelationship(team.getId(), user.getId(), Entity.TEAM, USER, Relationship.HAS);
    }
    if (teams.size() > 1) {
      // Remove organization team from the response
      teams =
          teams.stream()
              .filter(t -> !t.getId().equals(getOrganization().getId()))
              .collect(Collectors.toList());
      user.setTeams(teams);
    }
  }

  private void assignPersonas(User user, List personas) {
    for (EntityReference persona : listOrEmpty(personas)) {
      addRelationship(persona.getId(), user.getId(), Entity.PERSONA, USER, Relationship.APPLIED_TO);
    }
  }

  private void assignDefaultPersona(User user, EntityReference persona) {
    if (persona != null) {
      addRelationship(
          persona.getId(), user.getId(), Entity.PERSONA, USER, Relationship.DEFAULTS_TO);
    }
  }

  public static class UserCsv extends EntityCsv {
    public static final CsvDocumentation DOCUMENTATION = getCsvDocumentation(USER);
    public static final List HEADERS = DOCUMENTATION.getHeaders();
    public final Team team;

    UserCsv(Team importingTeam, String updatedBy) {
      super(USER, HEADERS, updatedBy);
      this.team = importingTeam;
    }

    @Override
    protected void createEntity(CSVPrinter printer, List csvRecords) throws IOException {
      CSVRecord csvRecord = getNextRecord(printer, csvRecords);
      // Field 1, 2, 3, 4, 5, 6 - name, displayName, description, email, timezone, isAdmin
      User user =
          UserUtil.getUser(
                  importedBy,
                  new CreateUser()
                      .withName(csvRecord.get(0))
                      .withDisplayName(csvRecord.get(1))
                      .withDescription(csvRecord.get(2))
                      .withEmail(csvRecord.get(3))
                      .withTimezone(csvRecord.get(4))
                      .withIsAdmin(getBoolean(printer, csvRecord, 5)))
              .withTeams(getTeams(printer, csvRecord, csvRecord.get(0)))
              .withRoles(getEntityReferences(printer, csvRecord, 7, ROLE));
      if (processRecord) {
        createUserEntity(printer, csvRecord, user);
      }
    }

    @Override
    protected void addRecord(CsvFile csvFile, User entity) {
      // Headers - name,displayName,description,email,timezone,isAdmin,team,roles
      List recordList = new ArrayList<>();
      addField(recordList, entity.getName());
      addField(recordList, entity.getDisplayName());
      addField(recordList, entity.getDescription());
      addField(recordList, entity.getEmail());
      addField(recordList, entity.getTimezone());
      addField(recordList, entity.getIsAdmin());
      addField(recordList, entity.getTeams().get(0).getFullyQualifiedName());
      addEntityReferences(recordList, entity.getRoles());
      addRecord(csvFile, recordList);
    }

    private List listUsers(
        TeamRepository teamRepository,
        UserRepository userRepository,
        String parentTeam,
        List users,
        Fields fields) {
      // Export the users by listing users for the entire team hierarchy
      ListFilter filter = new ListFilter(Include.NON_DELETED).addQueryParam("team", parentTeam);

      // Add users for the given team
      List userList = userRepository.listAll(fields, filter);
      if (!nullOrEmpty(userList)) {
        users.addAll(userList);
      }

      filter = new ListFilter(Include.NON_DELETED).addQueryParam("parentTeam", parentTeam);
      List teamList = teamRepository.listAll(Fields.EMPTY_FIELDS, filter);
      for (Team teamEntry : teamList) {
        listUsers(teamRepository, userRepository, teamEntry.getName(), users, fields);
      }
      return users;
    }

    public String exportCsv() throws IOException {
      UserRepository userRepository = (UserRepository) Entity.getEntityRepository(USER);
      TeamRepository teamRepository = (TeamRepository) Entity.getEntityRepository(TEAM);
      final Fields fields = userRepository.getFields("roles,teams");
      return exportCsv(
          listUsers(teamRepository, userRepository, team.getName(), new ArrayList<>(), fields));
    }

    private List getTeams(CSVPrinter printer, CSVRecord csvRecord, String user)
        throws IOException {
      List teams = getEntityReferences(printer, csvRecord, 6, Entity.TEAM);

      // Validate team being created is under the hierarchy of the team for which CSV is being
      // imported to
      for (EntityReference teamRef : listOrEmpty(teams)) {
        if (teamRef.getName().equals(team.getName())) {
          continue; // Team is same as the team to which CSV is being imported, then it is in the
          // same hierarchy
        }
        // Else the parent should already exist
        if (!SubjectContext.isInTeam(team.getName(), teamRef)) {
          importFailure(
              printer, invalidTeam(6, team.getName(), user, teamRef.getName()), csvRecord);
          processRecord = false;
        }
      }
      return teams;
    }

    public static String invalidTeam(int field, String team, String user, String userTeam) {
      String error =
          String.format("Team %s of user %s is not under %s team hierarchy", userTeam, user, team);
      return String.format(
          "#%s: Field %d error - %s", CsvErrorType.INVALID_FIELD, field + 1, error);
    }
  }

  @Override
  protected void postDelete(User entity) {
    // If the User is bot it's token needs to be invalidated
    if (Boolean.TRUE.equals(entity.getIsBot())) {
      BotTokenCache.invalidateToken(entity.getName());
    }
  }

  /** Handles entity updated from PUT and POST operation. */
  public class UserUpdater extends EntityUpdater {
    public UserUpdater(User original, User updated, Operation operation) {
      super(original, updated, operation);
    }

    @Transaction
    @Override
    public void entitySpecificUpdate() {
      // LowerCase Email
      updated.setEmail(original.getEmail().toLowerCase());

      // Updates
      updateRoles(original, updated);
      updateTeams(original, updated);
      updatePersonas(original, updated);
      recordChange(
          "defaultPersona", original.getDefaultPersona(), updated.getDefaultPersona(), true);
      recordChange("profile", original.getProfile(), updated.getProfile(), true);
      recordChange("timezone", original.getTimezone(), updated.getTimezone());
      recordChange("isBot", original.getIsBot(), updated.getIsBot());
      recordChange("isAdmin", original.getIsAdmin(), updated.getIsAdmin());
      recordChange("isEmailVerified", original.getIsEmailVerified(), updated.getIsEmailVerified());
      updateAuthenticationMechanism(original, updated);
    }

    private void updateRoles(User original, User updated) {
      // Remove roles from original and add roles from updated
      deleteFrom(original.getId(), USER, Relationship.HAS, Entity.ROLE);
      assignRoles(updated, updated.getRoles());

      List origRoles = listOrEmpty(original.getRoles());
      List updatedRoles = listOrEmpty(updated.getRoles());

      origRoles.sort(EntityUtil.compareEntityReference);
      updatedRoles.sort(EntityUtil.compareEntityReference);

      List added = new ArrayList<>();
      List deleted = new ArrayList<>();
      recordListChange(
          ROLES_FIELD, origRoles, updatedRoles, added, deleted, EntityUtil.entityReferenceMatch);
    }

    @Override
    protected void updateDomain() {
      if (operation.isPut() && !nullOrEmpty(original.getDomains()) && updatedByBot()) {
        // Revert change to non-empty domain if it is being updated by a bot
        // This is to prevent bots from overwriting the domain. Domain need to be
        // updated with a PATCH request
        updated.setDomains(original.getDomains());
        return;
      }

      List origDomains =
          EntityUtil.populateEntityReferences(listOrEmptyMutable(original.getDomains()));
      List updatedDomains =
          EntityUtil.populateEntityReferences(listOrEmptyMutable(updated.getDomains()));

      // Remove Domains for the user
      deleteTo(original.getId(), USER, Relationship.HAS, Entity.DOMAIN);

      for (EntityReference domain : updatedDomains) {
        addRelationship(domain.getId(), original.getId(), Entity.DOMAIN, USER, Relationship.HAS);
      }

      origDomains.sort(EntityUtil.compareEntityReference);
      updatedDomains.sort(EntityUtil.compareEntityReference);

      List added = new ArrayList<>();
      List deleted = new ArrayList<>();
      recordListChange(
          FIELD_DOMAINS,
          origDomains,
          updatedDomains,
          added,
          deleted,
          EntityUtil.entityReferenceMatch);
    }

    private void updateTeams(User original, User updated) {
      // Remove teams from original and add teams from updated
      deleteTo(original.getId(), USER, Relationship.HAS, Entity.TEAM);
      assignTeams(updated, updated.getTeams());

      List origTeams = listOrEmpty(original.getTeams());
      List updatedTeams = listOrEmpty(updated.getTeams());

      origTeams.sort(EntityUtil.compareEntityReference);
      updatedTeams.sort(EntityUtil.compareEntityReference);

      List added = new ArrayList<>();
      List deleted = new ArrayList<>();
      recordListChange(
          TEAMS_FIELD, origTeams, updatedTeams, added, deleted, EntityUtil.entityReferenceMatch);
    }

    private void updatePersonas(User original, User updated) {
      deleteTo(original.getId(), USER, Relationship.APPLIED_TO, Entity.PERSONA);
      assignPersonas(updated, updated.getPersonas());

      List origPersonas = listOrEmpty(original.getPersonas());
      List updatedPersonas = listOrEmpty(updated.getPersonas());

      origPersonas.sort(EntityUtil.compareEntityReference);
      updatedPersonas.sort(EntityUtil.compareEntityReference);

      List added = new ArrayList<>();
      List deleted = new ArrayList<>();

      recordListChange(
          "personas",
          origPersonas,
          updatedPersonas,
          added,
          deleted,
          EntityUtil.entityReferenceMatch);
    }

    private void updateAuthenticationMechanism(User original, User updated) {
      AuthenticationMechanism origAuthMechanism = original.getAuthenticationMechanism();
      AuthenticationMechanism updatedAuthMechanism = updated.getAuthenticationMechanism();
      if (origAuthMechanism == null && updatedAuthMechanism != null) {
        recordChange(
            AUTH_MECHANISM_FIELD, original.getAuthenticationMechanism(), "new-encrypted-value");
      } else if (origAuthMechanism != null
          && updatedAuthMechanism != null
          && !JsonUtils.areEquals(origAuthMechanism, updatedAuthMechanism)) {
        recordChange(AUTH_MECHANISM_FIELD, "old-encrypted-value", "new-encrypted-value");
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy