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

io.fusionauth.domain.User Maven / Gradle / Ivy

There is a newer version: 1.53.0
Show newest version
/*
 * Copyright (c) 2019-2024, FusionAuth, All Rights Reserved
 *
 * 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 io.fusionauth.domain;

import java.net.URI;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.inversoft.json.JacksonConstructor;
import com.inversoft.json.ToString;
import io.fusionauth.domain.util.Normalizer;
import static io.fusionauth.domain.util.Normalizer.toLowerCase;
import static io.fusionauth.domain.util.Normalizer.trim;

/**
 * The global view of a User. This object contains all global information about the user including birthdate, registration information
 * preferred languages, global attributes, etc.
 *
 * @author Seth Musselman
 */
public class User extends SecureIdentity implements Buildable, Tenantable {
  public final List preferredLanguages = new ArrayList<>();

  private final List memberships = new ArrayList<>();

  private final List registrations = new ArrayList<>();

  public boolean active;

  public LocalDate birthDate;

  public UUID cleanSpeakId;

  public Map data = new LinkedHashMap<>();

  public String email;

  public ZonedDateTime expiry;

  public String firstName;

  public String fullName;

  public URI imageUrl;

  public ZonedDateTime insertInstant;

  public String lastName;

  public ZonedDateTime lastUpdateInstant;

  public String middleName;

  public String mobilePhone;

  public String parentEmail;

  public UUID tenantId;

  public ZoneId timezone;

  public UserTwoFactorConfiguration twoFactor = new UserTwoFactorConfiguration();

  @JacksonConstructor
  public User() {
  }

  public User(User other) {
    this.active = other.active;
    this.connectorId = other.connectorId;
    this.breachedPasswordLastCheckedInstant = other.breachedPasswordLastCheckedInstant;
    this.breachedPasswordStatus = other.breachedPasswordStatus;
    this.birthDate = other.birthDate;
    this.cleanSpeakId = other.cleanSpeakId;
    this.email = other.email;
    this.encryptionScheme = other.encryptionScheme;
    this.expiry = other.expiry;
    this.factor = other.factor;
    this.firstName = other.firstName;
    this.fullName = other.fullName;
    this.id = other.id;
    this.imageUrl = other.imageUrl;
    this.insertInstant = other.insertInstant;
    this.lastLoginInstant = other.lastLoginInstant;
    this.lastUpdateInstant = other.lastUpdateInstant;
    this.lastName = other.lastName;
    this.memberships.addAll(other.memberships.stream().map(GroupMember::new).collect(Collectors.toList()));
    this.middleName = other.middleName;
    this.mobilePhone = other.mobilePhone;
    this.parentEmail = other.parentEmail;
    this.password = other.password;
    this.passwordChangeReason = other.passwordChangeReason;
    this.passwordChangeRequired = other.passwordChangeRequired;
    this.passwordLastUpdateInstant = other.passwordLastUpdateInstant;
    this.preferredLanguages.addAll(other.preferredLanguages);
    this.registrations.addAll(other.registrations.stream().map(UserRegistration::new).collect(Collectors.toList()));
    this.salt = other.salt;
    this.tenantId = other.tenantId;
    this.timezone = other.timezone;
    this.twoFactor = new UserTwoFactorConfiguration(other.twoFactor);
    this.uniqueUsername = other.uniqueUsername;
    this.username = other.username;
    this.usernameStatus = other.usernameStatus;
    this.verified = other.verified;
    this.verifiedInstant = other.verifiedInstant;

    if (other.data != null) {
      this.data.putAll(other.data);
    }
  }

  /**
   * Safely adds a membership to a user. This ensures no duplicates.
   *
   * @param member The member.
   */
  public void addMemberships(GroupMember member) {
    memberships.removeIf(m -> m.groupId.equals(member.groupId));
    memberships.add(member);
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (!(o instanceof User)) {
      return false;
    }
    if (!super.equals(o)) {
      return false;
    }
    User user = (User) o;
    sort();
    user.sort();
    return active == user.active &&
           Objects.equals(preferredLanguages, user.preferredLanguages) &&
           Objects.equals(memberships, user.memberships) &&
           Objects.equals(registrations, user.registrations) &&
           Objects.equals(birthDate, user.birthDate) &&
           Objects.equals(cleanSpeakId, user.cleanSpeakId) &&
           Objects.equals(data, user.data) &&
           Objects.equals(email, user.email) &&
           Objects.equals(expiry, user.expiry) &&
           Objects.equals(firstName, user.firstName) &&
           Objects.equals(fullName, user.fullName) &&
           Objects.equals(imageUrl, user.imageUrl) &&
           Objects.equals(insertInstant, user.insertInstant) &&
           Objects.equals(lastName, user.lastName) &&
           Objects.equals(lastUpdateInstant, user.lastUpdateInstant) &&
           Objects.equals(middleName, user.middleName) &&
           Objects.equals(mobilePhone, user.mobilePhone) &&
           Objects.equals(parentEmail, user.parentEmail) &&
           Objects.equals(twoFactor, user.twoFactor) &&
           Objects.equals(tenantId, user.tenantId) &&
           Objects.equals(timezone, user.timezone);
  }

  @JsonIgnore
  public int getAge() {
    if (birthDate == null) {
      return -1;
    }

    return (int) birthDate.until(LocalDate.now(), ChronoUnit.YEARS);
  }

  public GroupMember getGroupMemberForGroup(UUID id) {
    return getMemberships().stream()
                           .filter(m -> m.id.equals(id))
                           .findFirst()
                           .orElse(null);
  }

  /**
   * @return return a single identity value preferring email over username.
   */
  @JsonIgnore
  public String getLogin() {
    return email == null ? uniqueUsername : email;
  }

  public List getMemberships() {
    return memberships;
  }

  @JsonIgnore
  public String getName() {
    if (fullName != null) {
      return fullName;
    }
    if (firstName != null) {
      return firstName + (lastName != null ? " " + lastName : "");
    }

    return null;
  }

  public UserRegistration getRegistrationForApplication(UUID id) {
    return getRegistrations().stream()
                             .filter(reg -> reg.applicationId.equals(id))
                             .findFirst()
                             .orElse(null);
  }

  public List getRegistrations() {
    return registrations;
  }

  public Set getRoleNamesForApplication(UUID id) {
    UserRegistration registration = getRegistrationForApplication(id);
    return registration != null ? registration.roles : null;
  }

  @Override
  public UUID getTenantId() {
    return tenantId;
  }

  /**
   * Return true if user data is provided for this user or any registrations.
   *
   * @return true if user data exists.
   */
  public boolean hasUserData() {
    if (!data.isEmpty()) {
      return true;
    }

    for (UserRegistration userRegistration : registrations) {
      if (userRegistration.hasRegistrationData()) {
        return true;
      }
    }

    return false;
  }

  @Override
  public int hashCode() {
    return Objects.hash(super.hashCode(), preferredLanguages, memberships, registrations, active, birthDate, cleanSpeakId, data, email, expiry, firstName, fullName, imageUrl, insertInstant, lastName, lastUpdateInstant, middleName, mobilePhone, parentEmail, tenantId, timezone, twoFactor);
  }

  /**
   * Attempt to retrieve the users email address first by checking the top level and then in user data.
   *
   * @return an email address or null if no email address is found.
   */
  public String lookupEmail() {
    if (email != null) {
      return email;
    } else if (data.containsKey("email")) {
      return data.get("email").toString();
    }
    return null;
  }

  public Locale lookupPreferredLanguage(UUID applicationId) {
    for (UserRegistration registration : registrations) {
      if (registration.applicationId.equals(applicationId)) {
        if (registration.preferredLanguages.size() > 0) {
          return registration.preferredLanguages.get(0);
        }
      }
    }

    if (preferredLanguages.size() > 0) {
      return preferredLanguages.get(0);
    }

    return null;
  }

  /**
   * Normalizes all the fields.
   */
  public void normalize() {
    Normalizer.removeEmpty(data);
    email = toLowerCase(trim(email));
    encryptionScheme = trim(encryptionScheme);
    firstName = trim(firstName);
    fullName = trim(fullName);
    lastName = trim(lastName);
    middleName = trim(middleName);
    mobilePhone = trim(mobilePhone);
    parentEmail = toLowerCase(trim(parentEmail));
    Normalizer.removeEmpty(preferredLanguages);
    Normalizer.deDuplicate(preferredLanguages);
    preferredLanguages.removeIf(l -> l.toString().equals(""));
    username = trim(username);
    if (username != null && username.length() == 0) {
      username = null;
    }

    // Clear out groups that don't have groupId
    memberships.removeIf(m -> m.groupId == null);

    // Normalize the registrations
    getRegistrations().forEach(UserRegistration::normalize);
  }

  public void removeMembershipById(UUID groupId) {
    memberships.removeIf(m -> m.groupId.equals(groupId));
  }

  /**
   * Clear out sensitive data. Password, salt, etc.
   *
   * @return this
   */
  public User secure() {
    encryptionScheme = null;
    factor = null;
    password = null;
    salt = null;
    twoFactor.secure();
    return this;
  }

  public User sort() {
    this.registrations.sort(Comparator.comparing(ur -> ur.applicationId));
    return this;
  }

  @Override
  public String toString() {
    return ToString.toString(this);
  }

  // Synthetic method and used for backwards compatibility for the API response.
  public boolean twoFactorEnabled() {
    return twoFactor.methods.size() > 0;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy