001/*
002 * SonarQube
003 * Copyright (C) 2009-2017 SonarSource SA
004 * mailto:info AT sonarsource DOT com
005 *
006 * This program is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * This program is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public License
017 * along with this program; if not, write to the Free Software Foundation,
018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
019 */
020package org.sonar.api.server.authentication;
021
022import com.google.common.base.Predicate;
023import java.util.HashSet;
024import java.util.Set;
025import javax.annotation.CheckForNull;
026import javax.annotation.Nonnull;
027import javax.annotation.Nullable;
028import javax.annotation.concurrent.Immutable;
029import org.sonar.api.CoreProperties;
030
031import static com.google.common.base.Preconditions.checkArgument;
032import static com.google.common.base.Preconditions.checkNotNull;
033import static com.google.common.collect.FluentIterable.from;
034import static org.apache.commons.lang.StringUtils.isNotBlank;
035import static org.sonar.api.user.UserGroupValidation.validateGroupName;
036
037/**
038 * User information provided by the Identity Provider to be register into the platform.
039 *
040 * @since 5.4
041 */
042@Immutable
043public final class UserIdentity {
044
045  private final String providerLogin;
046  private final String login;
047  private final String name;
048  private final String email;
049  private final boolean groupsProvided;
050  private final Set<String> groups;
051
052  private UserIdentity(Builder builder) {
053    this.providerLogin = builder.providerLogin;
054    this.login = builder.login;
055    this.name = builder.name;
056    this.email = builder.email;
057    this.groupsProvided = builder.groupsProvided;
058    this.groups = builder.groups;
059  }
060
061  /**
062   * Non-blank user login for the related {@link IdentityProvider}.
063   */
064  public String getProviderLogin() {
065    return providerLogin;
066  }
067
068  /**
069   * Non-blank user login, unique for the SonarQube platform.
070   * If two {@link IdentityProvider} define two users with the same login, then users are considered as identical.
071   */
072  public String getLogin() {
073    return login;
074  }
075
076  /**
077   * Non-blank display name. Uniqueness is not mandatory, even it's recommended for easier search of users
078   * in webapp.
079   */
080  public String getName() {
081    return name;
082  }
083
084  /**
085   * Optional non-blank email. If defined, then it must be unique among all the users defined by all
086   * {@link IdentityProvider}. If not unique, then authentication will fail.
087   */
088  @CheckForNull
089  public String getEmail() {
090    return email;
091  }
092
093  /**
094   * Return true if groups should be synchronized for this user.
095   *
096   * @since 5.5
097   */
098  public boolean shouldSyncGroups() {
099    return groupsProvided;
100  }
101
102  /**
103   * List of group membership of the user. Only existing groups in SonarQube will be synchronized.
104   *
105   * @since 5.5
106   */
107  public Set<String> getGroups() {
108    return groups;
109  }
110
111  public static Builder builder() {
112    return new Builder();
113  }
114
115  public static class Builder {
116    private String providerLogin;
117    private String login;
118    private String name;
119    private String email;
120    private boolean groupsProvided = false;
121    private Set<String> groups = new HashSet<>();
122
123    private Builder() {
124    }
125
126    /**
127     * @see UserIdentity#getProviderLogin()
128     */
129    public Builder setProviderLogin(String providerLogin) {
130      this.providerLogin = providerLogin;
131      return this;
132    }
133
134    /**
135     * @see UserIdentity#getLogin() ()
136     */
137    public Builder setLogin(String login) {
138      this.login = login;
139      return this;
140    }
141
142    /**
143     * @see UserIdentity#getName()
144     */
145    public Builder setName(String name) {
146      this.name = name;
147      return this;
148    }
149
150    /**
151     * @see UserIdentity#getEmail()
152     */
153    public Builder setEmail(@Nullable String email) {
154      this.email = email;
155      return this;
156    }
157
158    /**
159     * Set group membership of the user. This method should only be used when synchronization of groups should be done.
160     * <ul>
161     *   <li>When groups are not empty, group membership is synchronized when user logs in :
162     *   <ul>
163     *     <li>User won't belong anymore to a group that is not in the list (even the default group defined in {@link CoreProperties#CORE_DEFAULT_GROUP})</li>
164     *     <li>User will belong only to groups that exist in SonarQube</li>
165     *     <li>Groups that don't exist in SonarQube are silently ignored</li>
166     *   </ul>
167     *   <li>When groups are empty, user won't belong to any group</li>
168     * </ul>
169     *
170     * @throws NullPointerException when groups is null
171     * @since 5.5
172     */
173    public Builder setGroups(Set<String> groups) {
174      checkNotNull(groups, "Groups cannot be null, please don't use this method if groups should not be synchronized.");
175      from(groups).filter(ValidateGroupName.INSTANCE).toList();
176      this.groupsProvided = true;
177      this.groups = groups;
178      return this;
179    }
180
181    public UserIdentity build() {
182      validateProviderLogin(providerLogin);
183      validateLogin(login);
184      validateName(name);
185      validateEmail(email);
186      return new UserIdentity(this);
187    }
188
189    private static void validateProviderLogin(String providerLogin) {
190      checkArgument(isNotBlank(providerLogin), "Provider login must not be blank");
191      checkArgument(providerLogin.length() <= 255, "Provider login size is incorrect (maximum 255 characters)");
192    }
193
194    private static void validateLogin(String login) {
195      checkArgument(isNotBlank(login), "User login must not be blank");
196      checkArgument(login.length() <= 255 && login.length() >= 2, "User login size is incorrect (Between 2 and 255 characters)");
197    }
198
199    private static void validateName(String name) {
200      checkArgument(isNotBlank(name), "User name must not be blank");
201      checkArgument(name.length() <= 200, "User name size is too big (200 characters max)");
202    }
203
204    private static void validateEmail(@Nullable String email) {
205      checkArgument(email == null || email.length() <= 100, "User email size is too big (100 characters max)");
206    }
207  }
208
209  private enum ValidateGroupName implements Predicate<String> {
210    INSTANCE;
211
212    @Override
213    public boolean apply(@Nonnull String input) {
214      validateGroupName(input);
215      return true;
216    }
217  }
218}