User.java 44.7 KB
Newer Older
K
kohsuke 已提交
1 2 3
/*
 * The MIT License
 * 
4 5
 * Copyright (c) 2004-2012, Sun Microsystems, Inc., Kohsuke Kawaguchi, Erik Ramfelt,
 * Tom Huybrechts, Vincent Latombe
K
kohsuke 已提交
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
K
kohsuke 已提交
25 26
package hudson.model;

27
import com.google.common.base.Predicate;
28
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
29 30 31 32 33 34 35 36
import hudson.BulkChange;
import hudson.CopyOnWrite;
import hudson.Extension;
import hudson.ExtensionList;
import hudson.ExtensionPoint;
import hudson.FeedAdapter;
import hudson.Util;
import hudson.XmlFile;
37 38
import hudson.init.InitMilestone;
import hudson.init.Initializer;
K
kohsuke 已提交
39
import hudson.model.Descriptor.FormException;
40
import hudson.model.listeners.SaveableListener;
41 42 43
import hudson.security.ACL;
import hudson.security.AccessControlled;
import hudson.security.Permission;
44
import hudson.security.SecurityRealm;
K
Kohsuke Kawaguchi 已提交
45
import hudson.security.UserMayOrMayNotExistException;
46
import hudson.util.FormApply;
47
import hudson.util.FormValidation;
K
kohsuke 已提交
48 49 50
import hudson.util.RunList;
import hudson.util.XStream2;
import java.io.File;
51
import java.io.FileFilter;
52
import java.io.IOException;
53 54 55 56
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
K
kohsuke 已提交
57
import java.util.ArrayList;
58
import java.util.Arrays;
59
import java.util.Collection;
K
kohsuke 已提交
60
import java.util.Collections;
61
import java.util.Comparator;
62 63
import java.util.HashSet;
import java.util.Iterator;
K
kohsuke 已提交
64 65
import java.util.List;
import java.util.Map;
66
import java.util.Objects;
67
import java.util.Set;
68 69
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
70
import java.util.concurrent.ExecutionException;
71 72
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
K
kohsuke 已提交
73 74
import java.util.logging.Level;
import java.util.logging.Logger;
75 76
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
77
import javax.annotation.Nullable;
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
import javax.annotation.concurrent.GuardedBy;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import jenkins.model.IdStrategy;
import jenkins.model.Jenkins;
import jenkins.model.ModelObjectWithContextMenu;
import jenkins.security.ImpersonatingUserDetailsService;
import jenkins.security.LastGrantedAuthoritiesProperty;
import jenkins.security.UserDetailsCache;
import jenkins.util.SystemProperties;
import net.sf.json.JSONObject;
import org.acegisecurity.Authentication;
import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
import org.acegisecurity.userdetails.UserDetails;
import org.acegisecurity.userdetails.UsernameNotFoundException;
import org.apache.commons.io.filefilter.DirectoryFileFilter;
96
import org.apache.commons.lang.StringUtils;
97 98 99
import org.jenkinsci.Symbol;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
W
Wadeck Follonier 已提交
100
import org.kohsuke.stapler.StaplerProxy;
101 102 103 104 105 106
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import org.kohsuke.stapler.interceptor.RequirePOST;
import org.springframework.dao.DataAccessException;
K
kohsuke 已提交
107 108 109

/**
 * Represents a user.
K
kohsuke 已提交
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
 *
 * <p>
 * In Hudson, {@link User} objects are created in on-demand basis;
 * for example, when a build is performed, its change log is computed
 * and as a result commits from users who Hudson has never seen may be discovered.
 * When this happens, new {@link User} object is created.
 *
 * <p>
 * If the persisted record for an user exists, the information is loaded at
 * that point, but if there's no such record, a fresh instance is created from
 * thin air (this is where {@link UserPropertyDescriptor#newInstance(User)} is
 * called to provide initial {@link UserProperty} objects.
 *
 * <p>
 * Such newly created {@link User} objects will be simply GC-ed without
 * ever leaving the persisted record, unless {@link User#save()} method
 * is explicitly invoked (perhaps as a result of a browser submitting a
 * configuration.)
 *
 *
K
kohsuke 已提交
130 131
 * @author Kohsuke Kawaguchi
 */
K
kohsuke 已提交
132
@ExportedBean
W
Wadeck Follonier 已提交
133
public class User extends AbstractModelObject implements AccessControlled, DescriptorByNameOwner, Saveable, Comparable<User>, ModelObjectWithContextMenu, StaplerProxy {
134

135 136 137
    /**
     * The username of the 'unknown' user used to avoid null user references.
     */
138
    private static final String UNKNOWN_USERNAME = "unknown";
139

140 141 142 143
    /**
     * These usernames should not be used by real users logging into Jenkins. Therefore, we prevent
     * users with these names from being saved.
     */
144
    private static final String[] ILLEGAL_PERSISTED_USERNAMES = new String[]{ACL.ANONYMOUS_USERNAME,
145
            ACL.SYSTEM_USERNAME, UNKNOWN_USERNAME};
K
kohsuke 已提交
146 147 148 149 150 151 152 153 154
    private transient final String id;

    private volatile String fullName;

    private volatile String description;

    /**
     * List of {@link UserProperty}s configured for this project.
     */
K
kohsuke 已提交
155
    @CopyOnWrite
K
kohsuke 已提交
156 157 158
    private volatile List<UserProperty> properties = new ArrayList<UserProperty>();


159
    private User(String id, String fullName) {
K
kohsuke 已提交
160
        this.id = id;
161
        this.fullName = fullName;
K
kohsuke 已提交
162 163
        load();
    }
K
kohsuke 已提交
164

165 166 167 168 169
    /**
     * Returns the {@link jenkins.model.IdStrategy} for use with {@link User} instances. See
     * {@link hudson.security.SecurityRealm#getUserIdStrategy()}
     *
     * @return the {@link jenkins.model.IdStrategy} for use with {@link User} instances.
170
     * @since 1.566
171 172 173
     */
    @Nonnull
    public static IdStrategy idStrategy() {
J
Jesse Glick 已提交
174 175 176 177 178 179
        Jenkins j = Jenkins.getInstance();
        SecurityRealm realm = j.getSecurityRealm();
        if (realm == null) {
            return IdStrategy.CASE_INSENSITIVE;
        }
        return realm.getUserIdStrategy();
180 181
    }

182
    public int compareTo(User that) {
183
        return idStrategy().compare(this.id, that.id);
184 185
    }

K
kohsuke 已提交
186 187 188 189
    /**
     * Loads the other data from disk if it's available.
     */
    private synchronized void load() {
190 191
        properties.clear();

K
kohsuke 已提交
192 193 194 195 196 197 198 199
        XmlFile config = getConfigFile();
        try {
            if(config.exists())
                config.unmarshal(this);
        } catch (IOException e) {
            LOGGER.log(Level.SEVERE, "Failed to load "+config,e);
        }

200 201 202 203 204 205
        // remove nulls that have failed to load
        for (Iterator<UserProperty> itr = properties.iterator(); itr.hasNext();) {
            if(itr.next()==null)
                itr.remove();            
        }

K
kohsuke 已提交
206 207
        // allocate default instances if needed.
        // doing so after load makes sure that newly added user properties do get reflected
208
        for (UserPropertyDescriptor d : UserProperty.all()) {
K
kohsuke 已提交
209 210 211 212 213 214 215
            if(getProperty(d.clazz)==null) {
                UserProperty up = d.newInstance(this);
                if(up!=null)
                    properties.add(up);
            }
        }

K
kohsuke 已提交
216 217 218 219
        for (UserProperty p : properties)
            p.setUser(this);
    }

K
kohsuke 已提交
220
    @Exported
K
kohsuke 已提交
221 222 223 224
    public String getId() {
        return id;
    }

225
    public @Nonnull String getUrl() {
226
        return "user/"+Util.rawEncode(idStrategy().keyFor(id));
227 228
    }

229
    public @Nonnull String getSearchUrl() {
230
        return "/user/"+Util.rawEncode(idStrategy().keyFor(id));
K
kohsuke 已提交
231 232
    }

K
kohsuke 已提交
233 234 235
    /**
     * The URL of the user page.
     */
K
kohsuke 已提交
236
    @Exported(visibility=999)
237
    public @Nonnull String getAbsoluteUrl() {
238
        return Jenkins.getInstance().getRootUrl()+getUrl();
K
kohsuke 已提交
239 240
    }

K
kohsuke 已提交
241 242 243 244
    /**
     * Gets the human readable name of this user.
     * This is configurable by the user.
     */
K
kohsuke 已提交
245
    @Exported(visibility=999)
246
    public @Nonnull String getFullName() {
K
kohsuke 已提交
247 248 249
        return fullName;
    }

K
kohsuke 已提交
250
    /**
251 252
     * Sets the human readable name of the user.
     * If the input parameter is empty, the user's ID will be set.
K
kohsuke 已提交
253 254 255 256 257 258
     */
    public void setFullName(String name) {
        if(Util.fixEmptyAndTrim(name)==null)    name=id;
        this.fullName = name;
    }

K
kohsuke 已提交
259
    @Exported
260
    public @CheckForNull String getDescription() {
K
kohsuke 已提交
261 262 263
        return description;
    }

264 265 266 267 268 269 270 271 272

    /**
     * Sets the description of the user.
     * @since 1.609
     */
    public void setDescription(String description) {
        this.description = description;
    }

K
kohsuke 已提交
273 274 275 276 277 278 279
    /**
     * Gets the user properties configured for this user.
     */
    public Map<Descriptor<UserProperty>,UserProperty> getProperties() {
        return Descriptor.toMap(properties);
    }

280 281 282
    /**
     * Updates the user object by adding a property.
     */
283
    public synchronized void addProperty(@Nonnull UserProperty p) throws IOException {
284 285 286 287 288
        UserProperty old = getProperty(p.getClass());
        List<UserProperty> ps = new ArrayList<UserProperty>(properties);
        if(old!=null)
            ps.remove(old);
        ps.add(p);
289
        p.setUser(this);
290 291 292
        properties = ps;
        save();
    }
293

K
kohsuke 已提交
294
    /**
295
     * List of all {@link UserProperty}s exposed primarily for the remoting API.
K
kohsuke 已提交
296
     */
K
kohsuke 已提交
297
    @Exported(name="property",inline=true)
298
    public List<UserProperty> getAllProperties() {
299 300 301 302 303
        if (hasPermission(Jenkins.ADMINISTER)) {
            return Collections.unmodifiableList(properties);
        }

        return Collections.emptyList();
304
    }
K
kohsuke 已提交
305
    
K
kohsuke 已提交
306 307 308 309 310 311
    /**
     * Gets the specific property, or null.
     */
    public <T extends UserProperty> T getProperty(Class<T> clazz) {
        for (UserProperty p : properties) {
            if(clazz.isInstance(p))
312
                return clazz.cast(p);
K
kohsuke 已提交
313 314 315 316
        }
        return null;
    }

317 318
    /**
     * Creates an {@link Authentication} object that represents this user.
319 320 321 322 323 324 325 326
     *
     * This method checks with {@link SecurityRealm} if the user is a valid user that can login to the security realm.
     * If {@link SecurityRealm} is a kind that does not support querying information about other users, this will
     * use {@link LastGrantedAuthoritiesProperty} to pick up the granted authorities as of the last time the user has
     * logged in.
     *
     * @throws UsernameNotFoundException
     *      If this user is not a valid user in the backend {@link SecurityRealm}.
327
     * @since 1.419
328
     */
329
    public @Nonnull Authentication impersonate() throws UsernameNotFoundException {
330
        try {
331 332
            UserDetails u = new ImpersonatingUserDetailsService(
                    Jenkins.getInstance().getSecurityRealm().getSecurityComponents().userDetails).loadUserByUsername(id);
333
            return new UsernamePasswordAuthenticationToken(u.getUsername(), "", u.getAuthorities());
334 335
        } catch (UserMayOrMayNotExistException e) {
            // backend can't load information about other users. so use the stored information if available
336
        } catch (UsernameNotFoundException e) {
337
            // if the user no longer exists in the backend, we need to refuse impersonating this user
K
Kohsuke Kawaguchi 已提交
338 339
            if (!ALLOW_NON_EXISTENT_USER_TO_LOGIN)
                throw e;
340
        } catch (DataAccessException e) {
341
            // seems like it's in the same boat as UserMayOrMayNotExistException
342
        }
343 344

        // seems like a legitimate user we have no idea about. proceed with minimum access
345 346
        return new UsernamePasswordAuthenticationToken(id, "",
            new GrantedAuthority[]{SecurityRealm.AUTHENTICATED_AUTHORITY});
347 348
    }

K
kohsuke 已提交
349 350 351
    /**
     * Accepts the new description.
     */
352
    @RequirePOST
K
kohsuke 已提交
353
    public synchronized void doSubmitDescription( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
354
        checkPermission(Jenkins.ADMINISTER);
K
kohsuke 已提交
355 356 357 358 359 360 361

        description = req.getParameter("description");
        save();
        
        rsp.sendRedirect(".");  // go to the top page
    }

K
kohsuke 已提交
362 363 364 365 366
    /**
     * Gets the fallback "unknown" user instance.
     * <p>
     * This is used to avoid null {@link User} instance.
     */
367
    public static @Nonnull User getUnknown() {
368
        return getById(UNKNOWN_USERNAME, true);
369
    }
K
kohsuke 已提交
370

K
kohsuke 已提交
371
    /**
372
     * Gets the {@link User} object by its id or full name.
K
kohsuke 已提交
373 374 375 376 377 378
     *
     * @param create
     *      If true, this method will never return null for valid input
     *      (by creating a new {@link User} object if none exists.)
     *      If false, this method will return null if {@link User} object
     *      with the given name doesn't exist.
379 380
     * @return Requested user. May be {@code null} if a user does not exist and
     *      {@code create} is false.
381
     * @deprecated use {@link User#get(String, boolean, java.util.Map)}
K
kohsuke 已提交
382
     */
383
    @Deprecated
384
    public static @Nullable User get(String idOrFullName, boolean create) {
385 386 387 388 389 390 391 392 393 394 395 396 397 398 399
        return get(idOrFullName, create, Collections.emptyMap());
    }

    /**
     * Gets the {@link User} object by its id or full name.
     *
     * @param create
     *      If true, this method will never return null for valid input
     *      (by creating a new {@link User} object if none exists.)
     *      If false, this method will return null if {@link User} object
     *      with the given name doesn't exist.
     *
     * @param context
     *      contextual environment this user idOfFullName was retrieved from,
     *      that can help resolve the user ID
400 401 402 403
     * 
     * @return
     *      An existing or created user. May be {@code null} if a user does not exist and
     *      {@code create} is false.
404
     */
405
    public static @Nullable User get(String idOrFullName, boolean create, Map context) {
406

407
        if(idOrFullName==null)
K
kohsuke 已提交
408
            return null;
409 410

        // sort resolvers by priority
411
        List<CanonicalIdResolver> resolvers = new ArrayList<CanonicalIdResolver>(ExtensionList.lookup(CanonicalIdResolver.class));
412 413 414
        Collections.sort(resolvers);

        String id = null;
N
Nicolas De Loof 已提交
415 416
        for (CanonicalIdResolver resolver : resolvers) {
            id = resolver.resolveCanonicalId(idOrFullName, context);
417 418 419 420
            if (id != null) {
                LOGGER.log(Level.FINE, "{0} mapped {1} to {2}", new Object[] {resolver, idOrFullName, id});
                break;
            }
421
        }
N
Nicolas De Loof 已提交
422
        // DefaultUserCanonicalIdResolver will always return a non-null id if all other CanonicalIdResolver failed
423 424 425
        if (id == null) {
            throw new IllegalStateException("The user id should be always non-null thanks to DefaultUserCanonicalIdResolver");
        }
426 427 428 429
        return getOrCreate(id, idOrFullName, create);
    }

    /**
430 431 432 433
     * Retrieve a user by its ID, and create a new one if requested.
     * @return
     *      An existing or created user. May be {@code null} if a user does not exist and
     *      {@code create} is false.
434
     */
435
    private static @Nullable User getOrCreate(@Nonnull String id, @Nonnull String fullName, boolean create) {
436 437 438 439
        return getOrCreate(id, fullName, create, getUnsanitizedLegacyConfigFileFor(id));
    }

    private static @Nullable User getOrCreate(@Nonnull String id, @Nonnull String fullName, boolean create, File unsanitizedLegacyConfigFile) {
440
        String idkey = idStrategy().keyFor(id);
441

442 443 444
        byNameLock.readLock().lock();
        User u;
        try {
445
            u = AllUsers.byName().get(idkey);
446 447 448
        } finally {
            byNameLock.readLock().unlock();
        }
449
        final File configFile = getConfigFileFor(id);
W
Wadeck Follonier 已提交
450 451
        boolean mustMigrateLegacyConfig = isMigrationRequiredForLegacyConfigFile(unsanitizedLegacyConfigFile, configFile);
        if (mustMigrateLegacyConfig) {
452 453 454 455 456
            File ancestor = unsanitizedLegacyConfigFile.getParentFile();
            if (!configFile.exists()) {
                try {
                    Files.createDirectory(configFile.getParentFile().toPath());
                    Files.move(unsanitizedLegacyConfigFile.toPath(), configFile.toPath());
457
                    LOGGER.log(Level.INFO, "Migrated user record from {0} to {1}", new Object[] {unsanitizedLegacyConfigFile, configFile});
458 459 460
                } catch (IOException | InvalidPathException e) {
                    LOGGER.log(
                            Level.WARNING,
461
                            String.format("Failed to migrate user record from %s to %s", unsanitizedLegacyConfigFile, configFile),
462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487
                            e);
                }
            }

            // Don't clean up ancestors with other children; the directories should be cleaned up when the last child
            // is migrated
            File tmp = ancestor;
            try {
                while (!ancestor.equals(getRootDir())) {
                    try (DirectoryStream<Path> stream = Files.newDirectoryStream(ancestor.toPath())) {
                        if (!stream.iterator().hasNext()) {
                            tmp = ancestor;
                            ancestor = tmp.getParentFile();
                            Files.deleteIfExists(tmp.toPath());
                        } else {
                            break;
                        }
                    }
                }
            } catch (IOException | InvalidPathException e) {
                if (LOGGER.isLoggable(Level.FINE)) {
                    LOGGER.log(Level.FINE, "Could not delete " + tmp + " when cleaning up legacy user directories", e);
                }
            }
        }

488
        if (u==null && (create || configFile.exists())) {
489
            User tmp = new User(id, fullName);
490 491 492
            User prev;
            byNameLock.readLock().lock();
            try {
493
                prev = AllUsers.byName().putIfAbsent(idkey, u = tmp);
494 495 496
            } finally {
                byNameLock.readLock().unlock();
            }
497 498 499 500 501
            if (prev != null) {
                u = prev; // if some has already put a value in the map, use it
                if (LOGGER.isLoggable(Level.FINE) && !fullName.equals(prev.getFullName())) {
                    LOGGER.log(Level.FINE, "mismatch on fullName (‘" + fullName + "’ vs. ‘" + prev.getFullName() + "’) for ‘" + id + "’", new Throwable());
                }
502
            } else if (!id.equals(fullName) && !configFile.exists()) {
503 504 505 506 507 508
                // JENKINS-16332: since the fullName may not be recoverable from the id, and various code may store the id only, we must save the fullName
                try {
                    u.save();
                } catch (IOException x) {
                    LOGGER.log(Level.WARNING, null, x);
                }
509
            }
K
kohsuke 已提交
510
        }
511
        return u;
K
kohsuke 已提交
512
    }
W
Wadeck Follonier 已提交
513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543
    
    private static boolean isMigrationRequiredForLegacyConfigFile(@Nonnull File legacyConfigFile, @Nonnull File newConfigFile){
        boolean mustMigrateLegacyConfig = legacyConfigFile.exists() && !legacyConfigFile.equals(newConfigFile);
        if(mustMigrateLegacyConfig){
            try{
                // TODO Could be replace by Util.isDescendant(getRootDir(), legacyConfigFile) in 2.80+
                String canonicalLegacy = legacyConfigFile.getCanonicalPath();
                String canonicalUserDir = getRootDir().getCanonicalPath();
                if(!canonicalLegacy.startsWith(canonicalUserDir + File.separator)){
                    // without that check, the application config.xml could be moved (i.e. erased from application PoV)
                    mustMigrateLegacyConfig = false;
                    LOGGER.log(Level.WARNING, String.format(
                            "Attempt to escape from users directory with %s, migration aborted, see SECURITY-897 for more information",
                            legacyConfigFile.getAbsolutePath()
                    ));
                }
            }
            catch (IOException e){
                mustMigrateLegacyConfig = false;
                LOGGER.log(
                        Level.WARNING,
                        String.format(
                                "Failed to determine the canonical path of %s, migration aborted, see SECURITY-897 for more information", 
                                legacyConfigFile.getAbsolutePath()
                        ),
                        e
                );
            }
        }
        return mustMigrateLegacyConfig;
    }
K
kohsuke 已提交
544

K
kohsuke 已提交
545
    /**
546
     * Gets the {@link User} object by its id or full name.
547
     * Use {@link #getById} when you know you have an ID.
K
kohsuke 已提交
548
     */
549
    public static @Nonnull User get(String idOrFullName) {
550
        return get(idOrFullName,true);
K
kohsuke 已提交
551 552
    }

K
kohsuke 已提交
553 554 555 556 557
    /**
     * Gets the {@link User} object representing the currently logged-in user, or null
     * if the current user is anonymous.
     * @since 1.172
     */
558
    public static @CheckForNull User current() {
559 560 561 562 563 564 565 566 567 568 569 570
        return get(Jenkins.getAuthentication());
    }

    /**
     * Gets the {@link User} object representing the supplied {@link Authentication} or
     * {@code null} if the supplied {@link Authentication} is either anonymous or {@code null}
     * @param a the supplied {@link Authentication} .
     * @return a {@link User} object for the supplied {@link Authentication} or {@code null}
     * @since 1.609
     */
    public static @CheckForNull User get(@CheckForNull Authentication a) {
        if(a == null || a instanceof AnonymousAuthenticationToken)
K
kohsuke 已提交
571
            return null;
572 573 574

        // Since we already know this is a name, we can just call getOrCreate with the name directly.
        String id = a.getName();
575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591
        return getById(id, true);
    }

    /**
     * Gets the {@link User} object by its <code>id</code>
     *
     * @param id
     *            the id of the user to retrieve and optionally create if it does not exist.
     * @param create
     *            If <code>true</code>, this method will never return <code>null</code> for valid input (by creating a
     *            new {@link User} object if none exists.) If <code>false</code>, this method will return
     *            <code>null</code> if {@link User} object with the given id doesn't exist.
     * @return the a User whose id is <code>id</code>, or <code>null</code> if <code>create</code> is <code>false</code>
     *         and the user does not exist.
     */
    public static @Nullable User getById(String id, boolean create) {
        return getOrCreate(id, id, create);
K
kohsuke 已提交
592 593
    }

K
kohsuke 已提交
594 595 596
    /**
     * Gets all the users.
     */
597
    public static @Nonnull Collection<User> getAll() {
598
        final IdStrategy strategy = idStrategy();
599 600 601
        byNameLock.readLock().lock();
        ArrayList<User> r;
        try {
602
            r = new ArrayList<User>(AllUsers.byName().values());
603 604 605
        } finally {
            byNameLock.readLock().unlock();
        }
606
        Collections.sort(r,new Comparator<User>() {
607

608
            public int compare(User o1, User o2) {
609
                return strategy.compare(o1.getId(), o2.getId());
610 611 612
            }
        });
        return r;
K
kohsuke 已提交
613 614
    }

615
    /**
616
     * To be called from {@link Jenkins#reload} only.
617
     */
618
    @Restricted(NoExternalUse.class)
619
    public static void reload() {
620 621
        byNameLock.readLock().lock();
        try {
622
            AllUsers.byName().clear();
623 624 625
        } finally {
            byNameLock.readLock().unlock();
        }
626 627
        UserDetailsCache.get().invalidateAll();
        AllUsers.scanAll();
628 629
    }

630
    /**
631
     * @deprecated Used to be called by test harnesses; now ignored in that case.
632
     */
633
    @Deprecated
634
    public static void clear() {
635 636 637 638
        if (ExtensionList.lookup(AllUsers.class).isEmpty()) {
            // Historically this was called by JenkinsRule prior to startup. Ignore!
            return;
        }
639 640
        byNameLock.writeLock().lock();
        try {
641
            AllUsers.byName().clear();
642 643 644
        } finally {
            byNameLock.writeLock().unlock();
        }
645 646
    }

647 648
    /**
     * Called when changing the {@link IdStrategy}.
649
     * @since 1.566
650 651
     */
    public static void rekey() {
652 653 654
        final IdStrategy strategy = idStrategy();
        byNameLock.writeLock().lock();
        try {
655
            ConcurrentMap<String, User> byName = AllUsers.byName();
656 657 658 659 660 661 662
            for (Map.Entry<String, User> e : byName.entrySet()) {
                String idkey = strategy.keyFor(e.getValue().id);
                if (!idkey.equals(e.getKey())) {
                    // need to remap
                    byName.remove(e.getKey());
                    byName.putIfAbsent(idkey, e.getValue());
                }
663
            }
664 665
        } finally {
            byNameLock.writeLock().unlock();
666
            UserDetailsCache.get().invalidateAll();
667 668 669
        }
    }

K
kohsuke 已提交
670 671 672
    /**
     * Returns the user name.
     */
673
    public @Nonnull String getDisplayName() {
K
kohsuke 已提交
674 675 676
        return getFullName();
    }

677
    /** true if {@link AbstractBuild#hasParticipant} or {@link hudson.model.Cause.UserIdCause} */
678
    private boolean relatedTo(@Nonnull AbstractBuild<?,?> b) {
679 680 681 682 683 684
        if (b.hasParticipant(this)) {
            return true;
        }
        for (Cause cause : b.getCauses()) {
            if (cause instanceof Cause.UserIdCause) {
                String userId = ((Cause.UserIdCause) cause).getUserId();
685
                if (userId != null && idStrategy().equals(userId, getId())) {
686 687 688 689 690 691 692
                    return true;
                }
            }
        }
        return false;
    }

K
kohsuke 已提交
693 694 695 696
    /**
     * Gets the list of {@link Build}s that include changes by this user,
     * by the timestamp order.
     */
697
    @SuppressWarnings("unchecked")
698
    @WithBridgeMethods(List.class)
699
    public @Nonnull RunList getBuilds() {
B
Baptiste Mathus 已提交
700
        return RunList.fromJobs((Iterable)Jenkins.getInstance().allItems(Job.class)).filter(new Predicate<Run<?,?>>() {
701 702
            @Override public boolean apply(Run<?,?> r) {
                return r instanceof AbstractBuild && relatedTo((AbstractBuild<?,?>) r);
703
            }
704
        });
K
kohsuke 已提交
705 706
    }

707 708 709 710
    /**
     * Gets all the {@link AbstractProject}s that this user has committed to.
     * @since 1.191
     */
711
    public @Nonnull Set<AbstractProject<?,?>> getProjects() {
712
        Set<AbstractProject<?,?>> r = new HashSet<AbstractProject<?,?>>();
713
        for (AbstractProject<?,?> p : Jenkins.getInstance().allItems(AbstractProject.class))
714 715 716 717 718
            if(p.hasParticipant(this))
                r.add(p);
        return r;
    }

719
    public @Override String toString() {
K
kohsuke 已提交
720 721 722 723 724 725 726
        return fullName;
    }

    /**
     * The file we save our configuration.
     */
    protected final XmlFile getConfigFile() {
727 728 729 730
        return new XmlFile(XSTREAM,getConfigFileFor(id));
    }

    private static final File getConfigFileFor(String id) {
731
        return new File(getRootDir(), idStrategy().filenameOf(id) +"/config.xml");
732 733
    }

734 735 736 737
    private static File getUnsanitizedLegacyConfigFileFor(String id) {
        return new File(getRootDir(), idStrategy().legacyFilenameOf(id) + "/config.xml");
    }

738 739 740 741
    /**
     * Gets the directory where Hudson stores user information.
     */
    private static File getRootDir() {
742
        return new File(Jenkins.getInstance().getRootDir(), "users");
K
kohsuke 已提交
743 744
    }

745 746
    /**
     * Is the ID allowed? Some are prohibited for security reasons. See SECURITY-166.
J
Jesse Glick 已提交
747
     * <p>
748 749 750 751 752
     * Note that this is only enforced when saving. These users are often created
     * via the constructor (and even listed on /asynchPeople), but our goal is to
     * prevent anyone from logging in as these users. Therefore, we prevent
     * saving a User with one of these ids.
     *
753 754 755
     * @param id ID to be checked
     * @return {@code true} if the username or fullname is valid.
     *      For {@code null} or blank IDs returns {@code false}.
J
Jesse Glick 已提交
756
     * @since 1.600
757
     */
758
    public static boolean isIdOrFullnameAllowed(@CheckForNull String id) {
759
        //TODO: StringUtils.isBlank() checks the null value, but FindBugs is not smart enough. Remove it later
760 761 762
        if (id == null || StringUtils.isBlank(id)) {
            return false;
        }
763
        final String trimmedId = id.trim();
764
        for (String invalidId : ILLEGAL_PERSISTED_USERNAMES) {
765
            if (trimmedId.equalsIgnoreCase(invalidId))
766 767 768 769 770
                return false;
        }
        return true;
    }

K
kohsuke 已提交
771 772 773
    /**
     * Save the settings to a file.
     */
774 775 776 777 778 779 780
    public synchronized void save() throws IOException, FormValidation {
        if (! isIdOrFullnameAllowed(id)) {
            throw FormValidation.error(Messages.User_IllegalUsername(id));
        }
        if (! isIdOrFullnameAllowed(fullName)) {
            throw FormValidation.error(Messages.User_IllegalFullname(fullName));
        }
781
        if(BulkChange.contains(this))   return;
782
        getConfigFile().write(this);
783
        SaveableListener.fireOnChange(this, getConfigFile());
K
kohsuke 已提交
784 785
    }

J
Jesse Glick 已提交
786
    private Object writeReplace() {
787
        return XmlFile.replaceIfNotAtTopLevel(this, () -> new Replacer(this));
J
Jesse Glick 已提交
788 789 790 791 792 793 794 795 796 797 798
    }
    private static class Replacer {
        private final String id;
        Replacer(User u) {
            id = u.getId();
        }
        private Object readResolve() {
            return getById(id, false);
        }
    }

799 800 801 802 803 804 805
    /**
     * Deletes the data directory and removes this user from Hudson.
     *
     * @throws IOException
     *      if we fail to delete.
     */
    public synchronized void delete() throws IOException {
806 807 808
        final IdStrategy strategy = idStrategy();
        byNameLock.readLock().lock();
        try {
809
            AllUsers.byName().remove(strategy.keyFor(id));
810 811 812
        } finally {
            byNameLock.readLock().unlock();
        }
813
        Util.deleteRecursive(new File(getRootDir(), strategy.filenameOf(id)));
814
        UserDetailsCache.get().invalidate(strategy.keyFor(id));
815 816
    }

K
kohsuke 已提交
817 818 819 820 821 822 823
    /**
     * Exposed remote API.
     */
    public Api getApi() {
        return new Api(this);
    }

K
kohsuke 已提交
824 825 826
    /**
     * Accepts submission from the configuration page.
     */
827
    @RequirePOST
828
    public void doConfigSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, FormException {
829
        checkPermission(Jenkins.ADMINISTER);
K
kohsuke 已提交
830

831
        JSONObject json = req.getSubmittedForm();
832
        String oldFullName = this.fullName;
833 834 835
        fullName = json.getString("fullName");
        description = json.getString("description");

836 837 838 839
        List<UserProperty> props = new ArrayList<UserProperty>();
        int i = 0;
        for (UserPropertyDescriptor d : UserProperty.all()) {
            UserProperty p = getProperty(d.clazz);
840 841 842 843 844 845 846 847

            JSONObject o = json.optJSONObject("userProperty" + (i++));
            if (o!=null) {
                if (p != null) {
                    p = p.reconfigure(req, o);
                } else {
                    p = d.newInstance(req, o);
                }
K
Oops  
Kohsuke Kawaguchi 已提交
848
                p.setUser(this);
849
            }
K
kohsuke 已提交
850

851 852
            if (p!=null)
                props.add(p);
K
kohsuke 已提交
853
        }
854 855 856 857
        this.properties = props;

        save();

858 859 860 861
        if (oldFullName != null && !oldFullName.equals(this.fullName)) {
            UserDetailsCache.get().invalidate(oldFullName);
        }

862
        FormApply.success(".").generateResponse(req,rsp,this);
K
kohsuke 已提交
863 864
    }

865 866 867
    /**
     * Deletes this user from Hudson.
     */
868
    @RequirePOST
869
    public void doDoDelete(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
870
        checkPermission(Jenkins.ADMINISTER);
871
        if (idStrategy().equals(id, Jenkins.getAuthentication().getName())) {
872 873 874 875 876 877 878 879 880
            rsp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Cannot delete self");
            return;
        }

        delete();

        rsp.sendRedirect2("../..");
    }

881
    public void doRssAll(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
882
        rss(req, rsp, " all builds", getBuilds(), Run.FEED_ADAPTER);
K
kohsuke 已提交
883 884
    }

885
    public void doRssFailed(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
886
        rss(req, rsp, " regression builds", getBuilds().regressionOnly(), Run.FEED_ADAPTER);
K
kohsuke 已提交
887 888
    }

889 890
    public void doRssLatest(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
        final List<Run> lastBuilds = new ArrayList<Run>();
891
        for (AbstractProject<?,?> p : Jenkins.getInstance().allItems(AbstractProject.class)) {
892
            for (AbstractBuild<?,?> b = p.getLastBuild(); b != null; b = b.getPreviousBuild()) {
893
                if (relatedTo(b)) {
894 895 896 897 898
                    lastBuilds.add(b);
                    break;
                }
            }
        }
899 900 901 902 903 904 905 906
        // historically these have been reported sorted by project name, we switched to the lazy iteration
        // so we only have to sort the sublist of runs rather than the full list of irrelevant projects
        Collections.sort(lastBuilds, new Comparator<Run>() {
            @Override
            public int compare(Run o1, Run o2) {
                return Items.BY_FULL_NAME.compare(o1.getParent(), o2.getParent());
            }
        });
907
        rss(req, rsp, " latest build", RunList.fromRuns(lastBuilds), Run.FEED_ADAPTER_LATEST);
K
kohsuke 已提交
908 909
    }

910 911 912 913
    private void rss(StaplerRequest req, StaplerResponse rsp, String suffix, RunList runs, FeedAdapter adapter)
            throws IOException, ServletException {
        RSS.forwardToRss(getDisplayName()+ suffix, getUrl(), runs.newBuilds(), adapter, req, rsp);
    }
K
kohsuke 已提交
914 915

    /**
916
     * This lock is used to guard access to the {@link AllUsers#byName} map. Use
917 918 919 920 921 922
     * {@link java.util.concurrent.locks.ReadWriteLock#readLock()} for normal access and
     * {@link java.util.concurrent.locks.ReadWriteLock#writeLock()} for {@link #rekey()} or any other operation
     * that requires operating on the map as a whole.
     */
    private static final ReadWriteLock byNameLock = new ReentrantReadWriteLock();

K
kohsuke 已提交
923 924 925
    /**
     * Used to load/save user configuration.
     */
926
    public static final XStream2 XSTREAM = new XStream2();
K
kohsuke 已提交
927 928 929 930 931 932 933

    private static final Logger LOGGER = Logger.getLogger(User.class.getName());

    static {
        XSTREAM.alias("user",User.class);
    }

934
    public ACL getACL() {
935
        final ACL base = Jenkins.getInstance().getAuthorizationStrategy().getACL(this);
936
        // always allow a non-anonymous user full control of himself.
937 938
        return new ACL() {
            public boolean hasPermission(Authentication a, Permission permission) {
939
                return (idStrategy().equals(a.getName(), id) && !(a instanceof AnonymousAuthenticationToken))
940
                        || base.hasPermission(a, permission);
941 942
            }
        };
943 944
    }

945 946 947 948
    /**
     * With ADMINISTER permission, can delete users with persisted data but can't delete self.
     */
    public boolean canDelete() {
949
        final IdStrategy strategy = idStrategy();
950 951
        return hasPermission(Jenkins.ADMINISTER) && !strategy.equals(id, Jenkins.getAuthentication().getName())
                && new File(getRootDir(), strategy.filenameOf(id)).exists();
952 953
    }

954 955 956
    /**
     * Checks for authorities (groups) associated with this user.
     * If the caller lacks {@link Jenkins#ADMINISTER}, or any problems arise, returns an empty list.
957
     * {@link SecurityRealm#AUTHENTICATED_AUTHORITY} and the username, if present, are omitted.
J
Jesse Glick 已提交
958
     * @since 1.498
959
     * @return a possibly empty list
960
     */
961
    public @Nonnull List<String> getAuthorities() {
962 963 964 965
        if (!Jenkins.getInstance().hasPermission(Jenkins.ADMINISTER)) {
            return Collections.emptyList();
        }
        List<String> r = new ArrayList<String>();
966 967 968 969 970 971 972 973
        Authentication authentication;
        try {
            authentication = impersonate();
        } catch (UsernameNotFoundException x) {
            LOGGER.log(Level.FINE, "cannot look up authorities for " + id, x);
            return Collections.emptyList();
        }
        for (GrantedAuthority a : authentication.getAuthorities()) {
974 975 976 977
            if (a.equals(SecurityRealm.AUTHENTICATED_AUTHORITY)) {
                continue;
            }
            String n = a.getAuthority();
978
            if (n != null && !idStrategy().equals(n, id)) {
979
                r.add(n);
980 981
            }
        }
982
        Collections.sort(r, String.CASE_INSENSITIVE_ORDER);
983 984 985
        return r;
    }

986
    public Object getDynamic(String token) {
987
        for(Action action: getTransientActions()){
988
            if(Objects.equals(action.getUrlName(), token))
989 990 991
                return action;
        }
        for(Action action: getPropertyActions()){
992
            if(Objects.equals(action.getUrlName(), token))
993
                return action;
994 995 996
        }
        return null;
    }
997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024
    
    /**
     * Return all properties that are also actions.
     * 
     * @return the list can be empty but never null. read only.
     */
    public List<Action> getPropertyActions() {
        List<Action> actions = new ArrayList<Action>();
        for (UserProperty userProp : getProperties().values()) {
            if (userProp instanceof Action) {
                actions.add((Action) userProp);
            }
        }
        return Collections.unmodifiableList(actions);
    }
    
    /**
     * Return all transient actions associated with this user.
     * 
     * @return the list can be empty but never null. read only.
     */
    public List<Action> getTransientActions() {
        List<Action> actions = new ArrayList<Action>();
        for (TransientUserActionFactory factory: TransientUserActionFactory.all()) {
            actions.addAll(factory.createFor(this));
        }
        return Collections.unmodifiableList(actions);
    }
1025

1026 1027 1028
    public ContextMenu doContextMenu(StaplerRequest request, StaplerResponse response) throws Exception {
        return new ContextMenu().from(this,request,response);
    }
W
Wadeck Follonier 已提交
1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044

    @Override
    @Restricted(NoExternalUse.class)
    public Object getTarget() {
        if (!SKIP_PERMISSION_CHECK) {
            Jenkins.getInstance().checkPermission(Jenkins.READ);
        }
        return this;
    }

    /**
     * Escape hatch for StaplerProxy-based access control
     */
    @Restricted(NoExternalUse.class)
    public static /* Script Console modifiable */ boolean SKIP_PERMISSION_CHECK = Boolean.getBoolean(User.class.getName() + ".skipPermissionCheck");

1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057
    
    /**
     * Gets list of Illegal usernames, for which users should not be created.
     * Always includes users from {@link #ILLEGAL_PERSISTED_USERNAMES}
     * @return List of usernames
     */
    @Restricted(NoExternalUse.class)
    /*package*/ static Set<String> getIllegalPersistedUsernames() {
        // TODO: This method is designed for further extensibility via system properties. To be extended in a follow-up issue
        final Set<String> res = new HashSet<>();
        res.addAll(Arrays.asList(ILLEGAL_PERSISTED_USERNAMES));
        return res;
    }
1058

1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069
    /** Per-{@link Jenkins} holder of all known {@link User}s. */
    @Extension
    @Restricted(NoExternalUse.class)
    public static final class AllUsers {

        @Initializer(after = InitMilestone.JOB_LOADED) // so Jenkins.loadConfig has been called
        public static void scanAll() {
            IdStrategy strategy = idStrategy();
            File[] subdirs = getRootDir().listFiles((FileFilter) DirectoryFileFilter.INSTANCE);
            if (subdirs != null) {
                for (File subdir : subdirs) {
1070 1071
                    File configFile = new File(subdir, "config.xml");
                    if (configFile.exists()) {
1072
                        String name = strategy.idFromFilename(subdir.getName());
1073
                        getOrCreate(name, /* <init> calls load(), probably clobbering this anyway */name, true, configFile);
1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089
                    }
                }
            }
        }

        @GuardedBy("User.byNameLock")
        private final ConcurrentMap<String,User> byName = new ConcurrentHashMap<String, User>();

        /**
         * Keyed by {@link User#id}. This map is used to ensure
         * singleton-per-id semantics of {@link User} objects.
         *
         * The key needs to be generated by {@link IdStrategy#keyFor(String)}.
         */
        @GuardedBy("User.byNameLock")
        static ConcurrentMap<String,User> byName() {
1090
            return ExtensionList.lookupSingleton(AllUsers.class).byName;
1091 1092 1093 1094
        }

    }

1095
    public static abstract class CanonicalIdResolver extends AbstractDescribableImpl<CanonicalIdResolver> implements ExtensionPoint, Comparable<CanonicalIdResolver> {
1096

1097
        /**
1098
         * context key for realm (domain) where idOrFullName has been retrieved from.
1099
         * Can be used (for example) to distinguish ambiguous committer ID using the SCM URL.
N
Nicolas De Loof 已提交
1100
         * Associated Value is a {@link String}
1101 1102
         */
        public static final String REALM = "realm";
1103

N
Nicolas De Loof 已提交
1104
        public int compareTo(CanonicalIdResolver o) {
1105 1106 1107 1108 1109 1110 1111 1112 1113 1114
            // reverse priority order
            int i = getPriority();
            int j = o.getPriority();
            return i>j ? -1 : (i==j ? 0:1);
        }

        /**
         * extract user ID from idOrFullName with help from contextual infos.
         * can return <code>null</code> if no user ID matched the input
         */
N
Nicolas De Loof 已提交
1115
        public abstract @CheckForNull String resolveCanonicalId(String idOrFullName, Map<String, ?> context);
1116 1117 1118 1119 1120 1121

        public int getPriority() {
            return 1;
        }

    }
1122 1123 1124 1125 1126


    /**
     * Resolve user ID from full name
     */
K
Kohsuke Kawaguchi 已提交
1127
    @Extension @Symbol("fullName")
1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142
    public static class FullNameIdResolver extends CanonicalIdResolver {

        @Override
        public String resolveCanonicalId(String idOrFullName, Map<String, ?> context) {
            for (User user : getAll()) {
                if (idOrFullName.equals(user.getFullName())) return user.getId();
            }
            return null;
        }

        @Override
        public int getPriority() {
            return -1; // lower than default
        }
    }
1143

1144 1145

    /**
1146 1147
     * Tries to verify if an ID is valid.
     * If so, we do not want to even consider users who might have the same full name.
1148 1149 1150 1151 1152
     */
    @Extension
    @Restricted(NoExternalUse.class)
    public static class UserIDCanonicalIdResolver extends User.CanonicalIdResolver {

1153 1154
        private static /* not final */ boolean SECURITY_243_FULL_DEFENSE = 
                SystemProperties.getBoolean(User.class.getName() + ".SECURITY_243_FULL_DEFENSE", true);
1155

1156 1157 1158 1159 1160 1161 1162
        private static final ThreadLocal<Boolean> resolving = new ThreadLocal<Boolean>() {
            @Override
            protected Boolean initialValue() {
                return false;
            }
        };

1163 1164
        @Override
        public String resolveCanonicalId(String idOrFullName, Map<String, ?> context) {
1165 1166 1167
            User existing = getById(idOrFullName, false);
            if (existing != null) {
                return existing.getId();
1168
            }
1169
            if (SECURITY_243_FULL_DEFENSE) {
1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180
                if (!resolving.get()) {
                    resolving.set(true);
                    try {
                        UserDetails userDetails = UserDetailsCache.get().loadUserByUsername(idOrFullName);
                        return userDetails.getUsername();
                    } catch (UsernameNotFoundException x) {
                        LOGGER.log(Level.FINE, "not sure whether " + idOrFullName + " is a valid username or not", x);
                    } catch (DataAccessException | ExecutionException x) {
                        LOGGER.log(Level.FINE, "could not look up " + idOrFullName, x);
                    } finally {
                        resolving.set(false);
1181
                    }
1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194
                }
            }
            return null;
        }

        @Override
        public int getPriority() {
            // should always come first so that ID that are ids get mapped correctly
            return Integer.MAX_VALUE;
        }

    }

K
Kohsuke Kawaguchi 已提交
1195 1196 1197 1198 1199 1200 1201 1202 1203
    /**
     * Jenkins now refuses to let the user login if he/she doesn't exist in {@link SecurityRealm},
     * which was necessary to make sure users removed from the backend will get removed from the frontend.
     * <p>
     * Unfortunately this infringed some legitimate use cases of creating Jenkins-local users for
     * automation purposes. This escape hatch switch can be enabled to resurrect that behaviour.
     *
     * JENKINS-22346.
     */
1204
    public static boolean ALLOW_NON_EXISTENT_USER_TO_LOGIN = SystemProperties.getBoolean(User.class.getName()+".allowNonExistentUserToLogin");
1205

1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218
    /**
     * Jenkins historically created a (usually) ephemeral user record when an user with Overall/Administer permission
     * accesses a /user/arbitraryName URL.
     * <p>
     * Unfortunately this constitutes a CSRF vulnerability, as malicious users can make admins create arbitrary numbers
     * of ephemeral user records, so the behavior was changed in Jenkins 2.TODO / 2.32.2.
     * <p>
     * As some users may be relying on the previous behavior, setting this to true restores the previous behavior. This
     * is not recommended.
     *
     * SECURITY-406.
     */
    @Restricted(NoExternalUse.class)
1219
    public static boolean ALLOW_USER_CREATION_VIA_URL = SystemProperties.getBoolean(User.class.getName() + ".allowUserCreationViaUrl");
1220

1221
}