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;
029
030import static com.google.common.base.Preconditions.checkArgument;
031import static com.google.common.base.Preconditions.checkNotNull;
032import static com.google.common.collect.FluentIterable.from;
033import static org.apache.commons.lang.StringUtils.isNotBlank;
034import static org.sonar.api.user.UserGroupValidation.validateGroupName;
035
036/**
037 * User information provided by the Identity Provider to be register into the platform.
038 *
039 * @since 5.4
040 */
041@Immutable
042public final class UserIdentity {
043
044  private final String providerLogin;
045  private final String login;
046  private final String name;
047  private final String email;
048  private final boolean groupsProvided;
049  private final Set<String> groups;
050
051  private UserIdentity(Builder builder) {
052    this.providerLogin = builder.providerLogin;
053    this.login = builder.login;
054    this.name = builder.name;
055    this.email = builder.email;
056    this.groupsProvided = builder.groupsProvided;
057    this.groups = builder.groups;
058  }
059
060  /**
061   * Non-blank user login for the related {@link IdentityProvider}.
062   */
063  public String getProviderLogin() {
064    return providerLogin;
065  }
066
067  /**
068   * Non-blank user login, unique for the SonarQube platform.
069   * If two {@link IdentityProvider} define two users with the same login, then users are considered as identical.
070   */
071  public String getLogin() {
072    return login;
073  }
074
075  /**
076   * Non-blank display name. Uniqueness is not mandatory, even it's recommended for easier search of users
077   * in webapp.
078   */
079  public String getName() {
080    return name;
081  }
082
083  /**
084   * Optional non-blank email. If defined, then it must be unique among all the users defined by all
085   * {@link IdentityProvider}. If not unique, then authentication will fail.
086   */
087  @CheckForNull
088  public String getEmail() {
089    return email;
090  }
091
092  /**
093   * Return true if groups should be synchronized for this user.
094   *
095   * @since 5.5
096   */
097  public boolean shouldSyncGroups() {
098    return groupsProvided;
099  }
100
101  /**
102   * List of group membership of the user. Only existing groups in SonarQube will be synchronized.
103   *
104   * @since 5.5
105   */
106  public Set<String> getGroups() {
107    return groups;
108  }
109
110  public static Builder builder() {
111    return new Builder();
112  }
113
114  public static class Builder {
115    private String providerLogin;
116    private String login;
117    private String name;
118    private String email;
119    private boolean groupsProvided = false;
120    private Set<String> groups = new HashSet<>();
121
122    private Builder() {
123    }
124
125    /**
126     * @see UserIdentity#getProviderLogin()
127     */
128    public Builder setProviderLogin(String providerLogin) {
129      this.providerLogin = providerLogin;
130      return this;
131    }
132
133    /**
134     * @see UserIdentity#getLogin() ()
135     */
136    public Builder setLogin(String login) {
137      this.login = login;
138      return this;
139    }
140
141    /**
142     * @see UserIdentity#getName()
143     */
144    public Builder setName(String name) {
145      this.name = name;
146      return this;
147    }
148
149    /**
150     * @see UserIdentity#getEmail()
151     */
152    public Builder setEmail(@Nullable String email) {
153      this.email = email;
154      return this;
155    }
156
157    /**
158     * Set group membership of the user. This method should only be used when synchronization of groups should be done.
159     * <ul>
160     *   <li>When groups are not empty, group membership is synchronized when user logs in :
161     *   <ul>
162     *     <li>User won't belong anymore to a group that is not in the list (even the default group defined in 'sonar-users')</li>
163     *     <li>User will belong only to groups that exist in SonarQube</li>
164     *     <li>Groups that don't exist in SonarQube are silently ignored</li>
165     *   </ul>
166     *   <li>When groups are empty, user won't belong to any group</li>
167     * </ul>
168     *
169     * @throws NullPointerException when groups is null
170     * @since 5.5
171     */
172    public Builder setGroups(Set<String> groups) {
173      checkNotNull(groups, "Groups cannot be null, please don't use this method if groups should not be synchronized.");
174      from(groups).filter(ValidateGroupName.INSTANCE).toList();
175      this.groupsProvided = true;
176      this.groups = groups;
177      return this;
178    }
179
180    public UserIdentity build() {
181      validateProviderLogin(providerLogin);
182      validateLogin(login);
183      validateName(name);
184      validateEmail(email);
185      return new UserIdentity(this);
186    }
187
188    private static void validateProviderLogin(String providerLogin) {
189      checkArgument(isNotBlank(providerLogin), "Provider login must not be blank");
190      checkArgument(providerLogin.length() <= 255, "Provider login size is incorrect (maximum 255 characters)");
191    }
192
193    private static void validateLogin(String login) {
194      checkArgument(isNotBlank(login), "User login must not be blank");
195      checkArgument(login.length() <= 255 && login.length() >= 2, "User login size is incorrect (Between 2 and 255 characters)");
196    }
197
198    private static void validateName(String name) {
199      checkArgument(isNotBlank(name), "User name must not be blank");
200      checkArgument(name.length() <= 200, "User name size is too big (200 characters max)");
201    }
202
203    private static void validateEmail(@Nullable String email) {
204      checkArgument(email == null || email.length() <= 100, "User email size is too big (100 characters max)");
205    }
206  }
207
208  private enum ValidateGroupName implements Predicate<String> {
209    INSTANCE;
210
211    @Override
212    public boolean apply(@Nonnull String input) {
213      validateGroupName(input);
214      return true;
215    }
216  }
217}