/*
 * The MIT License
 * 
 * Copyright (c) 2004-2012, Sun Microsystems, Inc., Kohsuke Kawaguchi, Erik Ramfelt,
 * Vincent Latombe
 * 
 * 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.
 */
package hudson.model;

import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
import com.gargoylesoftware.htmlunit.WebAssert;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlPage;

import hudson.security.AbstractPasswordBasedSecurityRealm;
import hudson.security.AccessDeniedException2;
import hudson.security.GlobalMatrixAuthorizationStrategy;
import hudson.security.GroupDetails;
import hudson.security.HudsonPrivateSecurityRealm;
import hudson.security.Permission;
import hudson.security.UserMayOrMayNotExistException;
import hudson.tasks.MailAddressResolver;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.Locale;

import jenkins.model.IdStrategy;
import jenkins.model.Jenkins;
import jenkins.security.ApiTokenProperty;

import org.acegisecurity.AccessDeniedException;
import org.acegisecurity.Authentication;
import org.acegisecurity.AuthenticationException;
import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.context.SecurityContextHolder;
import org.acegisecurity.userdetails.UserDetails;
import org.acegisecurity.userdetails.UsernameNotFoundException;

import static org.junit.Assert.*;
import static org.junit.Assume.*;

import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.FakeChangeLogSCM;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.TestExtension;
import org.jvnet.hudson.test.recipes.LocalData;

public class UserTest {

    @Rule public JenkinsRule j = new JenkinsRule();

    public static class UserPropertyImpl extends UserProperty implements Action {

        private final String testString;
        private UserPropertyDescriptor descriptorImpl = new UserPropertyDescriptorImpl();
        
        public UserPropertyImpl(String testString) {
            this.testString = testString;
        }
        
        public String getTestString() {
            return testString;
        }

        @Override
        public UserPropertyDescriptor getDescriptor() {
            return descriptorImpl;
        }
        
        public String getIconFileName() {
          return "/images/24x24/gear.png";
        }

        public String getDisplayName() {
          return "UserPropertyImpl";
        }

        public String getUrlName() {
          return "userpropertyimpl";
        }
        
        public static class UserPropertyDescriptorImpl extends UserPropertyDescriptor {
          @Override
          public UserProperty newInstance(User user) {
              return null;
          }
      }
    }

    @Issue("JENKINS-2331")
    @Test public void userPropertySummaryAndActionAreShownInUserPage() throws Exception {
        
        UserProperty property = new UserPropertyImpl("NeedleInPage");
        UserProperty.all().add(property.getDescriptor());
        
        User user = User.get("user-test-case");
        user.addProperty(property);
        
        HtmlPage page = j.createWebClient().goTo("user/user-test-case");
        
        WebAssert.assertTextPresentInElement(page, "NeedleInPage", "main-panel");
        WebAssert.assertTextPresentInElement(page, ((Action) property).getDisplayName(), "side-panel");
        
    }
    
    /**
     * Asserts that the default user avatar can be fetched (ie no 404)
     */
    @Issue("JENKINS-7494")
    @Test public void defaultUserAvatarCanBeFetched() throws Exception {
        User user = User.get("avatar-user", true);
        HtmlPage page = j.createWebClient().goTo("user/" + user.getDisplayName());
        j.assertAllImageLoadSuccessfully(page);
    }

    @Test public void getAuthorities() throws Exception {
        JenkinsRule.DummySecurityRealm realm = j.createDummySecurityRealm();
        realm.addGroups("administrator", "admins");
        realm.addGroups("alice", "users");
        realm.addGroups("bob", "users", "lpadmin", "bob");
        j.jenkins.setSecurityRealm(realm);
        GlobalMatrixAuthorizationStrategy auth = new GlobalMatrixAuthorizationStrategy();
        auth.add(Jenkins.ADMINISTER, "admins");
        auth.add(Permission.READ, "users");
        j.jenkins.setAuthorizationStrategy(auth);
        SecurityContext seccon = SecurityContextHolder.getContext();
        Authentication orig = seccon.getAuthentication();
        try {
            seccon.setAuthentication(User.get("administrator").impersonate());
            assertEquals("[admins]", User.get("administrator").getAuthorities().toString());
            assertEquals("[users]", User.get("alice").getAuthorities().toString());
            assertEquals("[lpadmin, users]", User.get("bob").getAuthorities().toString());
            assertEquals("[]", User.get("MasterOfXaos").getAuthorities().toString());
            seccon.setAuthentication(User.get("alice").impersonate());
            assertEquals("[]", User.get("alice").getAuthorities().toString());
            assertEquals("[]", User.get("bob").getAuthorities().toString());
        } finally {
            seccon.setAuthentication(orig);
        }
    }
   
    @Test
    public void testGetUser() {
        User user = User.get("John Smith");
        User user2 = User.get("John Smith2");
        user2.setFullName("John Smith");
        assertNotSame("Users should not have the same id.", user.getId(), user2.getId());
        User.clear();
        User user3 = User.get("John Smith");
        user3.setFullName("Alice Smith");
        assertEquals("Users should not have the same id.", user.getId(), user3.getId());
        User user4 = User.get("Marie",false, Collections.EMPTY_MAP);
        assertNull("User should not be created because Marie does not exists.", user4);
    }

    @Test
    public void caseInsensitivity() {
        j.jenkins.setSecurityRealm(new IdStrategySpecifyingSecurityRealm(new IdStrategy.CaseInsensitive()));
        User user = User.get("john smith");
        User user2 = User.get("John Smith");
        assertSame("Users should have the same id.", user.getId(), user2.getId());
        assertEquals(user.getId(), User.idStrategy().idFromFilename(User.idStrategy().filenameOf(user.getId())));
        assertEquals(user2.getId(), User.idStrategy().idFromFilename(User.idStrategy().filenameOf(user2.getId())));
    }
    
    @Test
    public void caseSensitivity() {
        j.jenkins.setSecurityRealm(new IdStrategySpecifyingSecurityRealm(new IdStrategy.CaseSensitive()));
        User user = User.get("john smith");
        User user2 = User.get("John Smith");
        assertNotSame("Users should not have the same id.", user.getId(), user2.getId());
        assertEquals("john smith", User.idStrategy().keyFor(user.getId()));
        assertEquals("john smith", User.idStrategy().filenameOf(user.getId()));
        assertEquals("John Smith", User.idStrategy().keyFor(user2.getId()));
        assertEquals("~john ~smith", User.idStrategy().filenameOf(user2.getId()));
        assertEquals(user.getId(), User.idStrategy().idFromFilename(User.idStrategy().filenameOf(user.getId())));
        assertEquals(user2.getId(), User.idStrategy().idFromFilename(User.idStrategy().filenameOf(user2.getId())));
    }

    @Test
    public void caseSensitivityEmail() {
        j.jenkins.setSecurityRealm(new IdStrategySpecifyingSecurityRealm(new IdStrategy.CaseSensitiveEmailAddress()));
        User user = User.get("john.smith@acme.org");
        User user2 = User.get("John.Smith@acme.org");
        assertNotSame("Users should not have the same id.", user.getId(), user2.getId());
        assertEquals("john.smith@acme.org", User.idStrategy().keyFor(user.getId()));
        assertEquals("john.smith@acme.org", User.idStrategy().filenameOf(user.getId()));
        assertEquals("John.Smith@acme.org", User.idStrategy().keyFor(user2.getId()));
        assertEquals("~john.~smith@acme.org", User.idStrategy().filenameOf(user2.getId()));
        user2 = User.get("john.smith@ACME.ORG");
        assertEquals("Users should have the same id.", user.getId(), user2.getId());
        assertEquals("john.smith@acme.org", User.idStrategy().keyFor(user2.getId()));
        assertEquals("john.smith@acme.org", User.idStrategy().filenameOf(user2.getId()));
        assertEquals(user.getId(), User.idStrategy().idFromFilename(User.idStrategy().filenameOf(user.getId())));
        assertEquals(user2.getId(), User.idStrategy().idFromFilename(User.idStrategy().filenameOf(user2.getId())));
    }

    private static class IdStrategySpecifyingSecurityRealm extends HudsonPrivateSecurityRealm {
        private final IdStrategy idStrategy;
        IdStrategySpecifyingSecurityRealm(IdStrategy idStrategy) {
            super(true, false, null);
            this.idStrategy = idStrategy;
        }
        @Override
        public IdStrategy getUserIdStrategy() {
            return idStrategy;
        }
    }

    @Issue("JENKINS-24317")
    @LocalData
    @Test public void migration() throws Exception {
        assumeFalse("was not a problem on a case-insensitive FS to begin with", new File(j.jenkins.getRootDir(), "users/bob").isDirectory());
        User bob = User.get("bob");
        assertEquals("Bob Smith", bob.getFullName());
        assertEquals("Bob Smith", User.get("Bob").getFullName());
        assertEquals("nonexistent", User.get("nonexistent").getFullName());
        assertEquals("[bob]", Arrays.toString(new File(j.jenkins.getRootDir(), "users").list()));
    }

    @Test
    public void testAddAndGetProperty() throws IOException {
        User user = User.get("John Smith");  
        UserProperty prop = new SomeUserProperty();
        user.addProperty(prop);
        assertNotNull("User should have SomeUserProperty property.", user.getProperty(SomeUserProperty.class));
        assertEquals("UserProperty1 should be assigned to its descriptor", prop, user.getProperties().get(prop.getDescriptor()));
        assertTrue("User should should contain SomeUserProperty.", user.getAllProperties().contains(prop));
        User.reload();
        assertNotNull("User should have SomeUserProperty property.", user.getProperty(SomeUserProperty.class));
    }

    @Test
    public void testImpersonateAndCurrent() {
        j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
        User user = User.get("John Smith"); 
        assertNotSame("User John Smith should not be the current user.", User.current().getId(), user.getId());
        SecurityContextHolder.getContext().setAuthentication(user.impersonate()); 
        assertEquals("User John Smith should be the current user.", user.getId(), User.current().getId());
    }

    @Test
    public void testGetUnknown() {
        User user = User.get("John Smith"); 
        assertNotNull("User should not be null.", user);
    }

    @Test
    public void testGetAndGetAll() {
        User user = User.get("John Smith", false, Collections.emptyMap());
        assertNull("User John Smith should not be created.", user);
        assertFalse("Jenkins should not contain user John Smith.", User.getAll().contains(user));
        User user2 = User.get("John Smith2", true, Collections.emptyMap());
        assertNotNull("User John Smith2 should be created.", user2);
        assertTrue("Jenkins should contain user John Smith2.", User.getAll().contains(user2));
        user = User.get("John Smith2", false, Collections.emptyMap());
        assertNotNull("User John Smith should be created.", user);
        assertTrue("Jenkins should contain user John Smith.", User.getAll().contains(user));
    }
    
    @Test
    public void testReload() throws IOException{
        User user = User.get("John Smith", true, Collections.emptyMap());
        user.save();
        String config = user.getConfigFile().asString();
        config = config.replace("John Smith", "Alice Smith");
        PrintStream st = new PrintStream(user.getConfigFile().getFile());
        st.print(config);
        User.clear();
        assertEquals("User should have full name John Smith.", "John Smith", user.getFullName());
        User.reload();
        user = User.get(user.getId(), false, Collections.emptyMap());
        assertEquals("User should have full name Alice Smith.", "Alice Smith", user.getFullName());
    }

    @Test
    public void testClear() {
        User user = User.get("John Smith", true, Collections.emptyMap());
        assertNotNull("User should not be null.", user);
        user.clear();
        user = User.get("John Smith", false, Collections.emptyMap());
        assertNull("User should be null", user);       
    }

    @Test
    public void testGetBuildsAndGetProjects() throws Exception {
        User user = User.get("John Smith", true, Collections.emptyMap());
        FreeStyleProject project = j.createFreeStyleProject("free");
        FreeStyleProject project2 = j.createFreeStyleProject("free2");
        project.save();
        FakeChangeLogSCM scm = new FakeChangeLogSCM();
        scm.addChange().withAuthor(user.getId());
        project.setScm(scm);
        j.buildAndAssertSuccess(project);
        j.buildAndAssertSuccess(project2);
        Build build = project.getLastBuild();
        Build build2 = project2.getLastBuild();
        assertTrue("User should participate in the last build of project free.", user.getBuilds().contains(build));
        assertFalse("User should not participate in the last build of project free2.", user.getBuilds().contains(build2));
        assertTrue("User should participate in the project free.", user.getProjects().contains(project));
        assertFalse("User should not participate in the project free2.", user.getProjects().contains(project2));

        //JENKINS-16178: build should include also builds scheduled by user

        build2.replaceAction(new CauseAction(new Cause.UserIdCause()));
        assertFalse("User should not participate in the last build of project free2.", user.getBuilds().contains(build2));
        assertFalse("Current user should not participate in the last build of project free.", User.current().getBuilds().contains(build));
        assertTrue("Current user should participate in the last build of project free2.", User.current().getBuilds().contains(build2));
    }

    @Test
    public void testSave() throws IOException {
        User user = User.get("John Smith", true, Collections.emptyMap());
        User.clear();
        User.reload();
        user = User.get("John Smith", false, Collections.emptyMap());
        assertNull("User should be null.", user);
        user = User.get("John Smithl", true, Collections.emptyMap());
        user.addProperty(new SomeUserProperty());
        user.save();
        User.clear();
        User.reload();
        user = User.get("John Smithl", false, Collections.emptyMap());
        assertNotNull("User should not be null.", user);
        assertNotNull("User should be saved with all changes.", user.getProperty(SomeUserProperty.class));
    }

    @Issue("JENKINS-16332")
    @Test public void unrecoverableFullName() throws Throwable {
        User u = User.get("John Smith <jsmith@nowhere.net>");
        assertEquals("jsmith@nowhere.net", MailAddressResolver.resolve(u));
        String id = u.getId();
        User.clear(); // simulate Jenkins restart
        u = User.get(id);
        assertEquals("jsmith@nowhere.net", MailAddressResolver.resolve(u));
    }

    @Test
    public void testDelete() throws IOException {
         User user = User.get("John Smith", true, Collections.emptyMap());
         user.save();
         user.delete();
         assertFalse("User should be deleted with his persistent data.", user.getConfigFile().exists());
         assertFalse("User should be deleted from memory.", User.getAll().contains(user));
         user = User.get("John Smith", false, Collections.emptyMap());
         assertNull("User should be deleted from memory.", user);
         User.reload();
         boolean contained = false;
         for(User u: User.getAll()){
             if(u.getId().equals(user.getId())){
                 contained = true;
                 break;
             }
         }
         assertFalse("User should not be loaded.", contained);
    }

    @Test
    public void testDoConfigSubmit() throws Exception {
        GlobalMatrixAuthorizationStrategy auth = new GlobalMatrixAuthorizationStrategy();   
        j.jenkins.setAuthorizationStrategy(auth);
        j.jenkins.setCrumbIssuer(null);
        HudsonPrivateSecurityRealm realm = new HudsonPrivateSecurityRealm(false);
        j.jenkins.setSecurityRealm(realm);
        User user = realm.createAccount("John Smith", "password");
        User user2 = realm.createAccount("John Smith2", "password");
        user2.save();
        auth.add(Jenkins.ADMINISTER, user.getId());
        auth.add(Jenkins.READ, user2.getId());
        SecurityContextHolder.getContext().setAuthentication(user.impersonate());
        HtmlForm form = j.createWebClient().login(user.getId(), "password").goTo(user2.getUrl() + "/configure").getFormByName("config");
        form.getInputByName("_.fullName").setValueAttribute("Alice Smith");
        j.submit(form);
        assertEquals("User should have full name Alice Smith.", "Alice Smith", user2.getFullName());
        SecurityContextHolder.getContext().setAuthentication(user2.impersonate());
        try{
            user.doConfigSubmit(null, null);
            fail("User should not have permission to configure another user.");
        }
        catch(Exception e){
            if(!(e.getClass().isAssignableFrom(AccessDeniedException2.class))){
               fail("AccessDeniedException should be thrown.");
            }
        }
        form = j.createWebClient().login(user2.getId(), "password").goTo(user2.getUrl() + "/configure").getFormByName("config");
        
        form.getInputByName("_.fullName").setValueAttribute("John");
        j.submit(form);
        assertEquals("User should be albe to configure himself.", "John", user2.getFullName());

    }

    @Test
    public void testDoDoDelete() throws Exception {
        GlobalMatrixAuthorizationStrategy auth = new GlobalMatrixAuthorizationStrategy();   
        j.jenkins.setAuthorizationStrategy(auth);
        j.jenkins.setCrumbIssuer(null);
        HudsonPrivateSecurityRealm realm = new HudsonPrivateSecurityRealm(false);
        j.jenkins.setSecurityRealm(realm);
        User user = realm.createAccount("John Smith", "password");
        User user2 = realm.createAccount("John Smith2", "password");
        user2.save();
        auth.add(Jenkins.ADMINISTER, user.getId());
        auth.add(Jenkins.READ, user2.getId());
        SecurityContextHolder.getContext().setAuthentication(user.impersonate());
        HtmlForm form = j.createWebClient().login(user.getId(), "password").goTo(user2.getUrl() + "/delete").getFormByName("delete");
        j.submit(form);
        assertFalse("User should be deleted from memory.", User.getAll().contains(user2));
        assertFalse("User should be deleted with his persistent data.", user2.getConfigFile().exists());
        User.reload();
        assertNull("Deleted user should not be loaded.", User.get(user2.getId(),false, Collections.EMPTY_MAP));
        user2 = realm.createAccount("John Smith2", "password");
        SecurityContextHolder.getContext().setAuthentication(user2.impersonate());
        try{
            user.doDoDelete(null, null);
            fail("User should not have permission to delete another user.");
        }
        catch(Exception e){
            if(!(e.getClass().isAssignableFrom(AccessDeniedException2.class))){
               fail("AccessDeniedException should be thrown.");
            }
        }
        user.save();
        JenkinsRule.WebClient client = j.createWebClient();
        form = client.login(user.getId(), "password").goTo(user.getUrl() + "/delete").getFormByName("delete");
        try{
            j.submit(form);
            fail("User should not be able to delete himself");
        }
        catch(FailingHttpStatusCodeException e){
            //ok exception should be thrown
            Assert.assertEquals(400, e.getStatusCode());
        }
        assertTrue("User should not delete himself from memory.", User.getAll().contains(user));
        assertTrue("User should not delete his persistent data.", user.getConfigFile().exists());
        User.reload();
        assertNotNull("Deleted user should be loaded.",User.get(user.getId(),false, Collections.EMPTY_MAP));     
    }

    @Test
    public void testHasPermission() throws IOException {
        GlobalMatrixAuthorizationStrategy auth = new GlobalMatrixAuthorizationStrategy();   
        j.jenkins.setAuthorizationStrategy(auth);
        j.jenkins.setCrumbIssuer(null);
        HudsonPrivateSecurityRealm realm = new HudsonPrivateSecurityRealm(false);
        j.jenkins.setSecurityRealm(realm);
        User user = realm.createAccount("John Smith","password");
        User user2 = realm.createAccount("John Smith2", "password");
        SecurityContextHolder.getContext().setAuthentication(user.impersonate());
        assertFalse("Current user should not have permission read.", user2.hasPermission(Permission.READ));
        assertTrue("Current user should always have permission read to himself.", user.hasPermission(Permission.READ));
        auth.add(Jenkins.ADMINISTER, user.getId());
        assertTrue("Current user should have permission read, because he has permission administer.", user2.hasPermission(Permission.READ));
        SecurityContextHolder.getContext().setAuthentication(Jenkins.ANONYMOUS);
        user2 = User.get("anonymous");
        assertFalse("Current user should not have permission read, because does not have global permission read and authentication is anonymous.", user2.hasPermission(Permission.READ));
    }

    @Test
    public void testCanDelete() throws IOException {
        GlobalMatrixAuthorizationStrategy auth = new GlobalMatrixAuthorizationStrategy();   
        j.jenkins.setAuthorizationStrategy(auth);
        j.jenkins.setCrumbIssuer(null);
        HudsonPrivateSecurityRealm realm = new HudsonPrivateSecurityRealm(false);
        j.jenkins.setSecurityRealm(realm);
        User user = realm.createAccount("John Smith","password");
        User user2 = realm.createAccount("John Smith2","password");
        user2.save();

        SecurityContextHolder.getContext().setAuthentication(user.impersonate());
        assertFalse("Ordinary user cannot delete somebody else", user2.canDelete());
        auth.add(Jenkins.ADMINISTER, user.getId());
        assertTrue("Administrator can delete anybody else", user2.canDelete());
        assertFalse("User (even admin) cannot delete himself", user.canDelete());

        SecurityContextHolder.getContext().setAuthentication(user2.impersonate());
        auth.add(Jenkins.ADMINISTER, user2.getId());
        User user3 = User.get("Random Somebody");
        assertFalse("Storage-less temporary user cannot be deleted", user3.canDelete());
        user3.save();
        assertTrue("But once storage is allocated, he can be deleted", user3.canDelete());
    }

    @Test
    // @Issue("SECURITY-180")
    public void security180() throws Exception {
        final GlobalMatrixAuthorizationStrategy auth = new GlobalMatrixAuthorizationStrategy();
        j.jenkins.setAuthorizationStrategy(auth);
        j.jenkins.setSecurityRealm(j.createDummySecurityRealm());

        User alice = User.get("alice");
        User bob = User.get("bob");
        User admin = User.get("admin");

        auth.add(Jenkins.READ, alice.getId());
        auth.add(Jenkins.READ, bob.getId());
        auth.add(Jenkins.ADMINISTER, admin.getId());

        // Admin can change everyone's token
        SecurityContextHolder.getContext().setAuthentication(admin.impersonate());
        admin.getProperty(ApiTokenProperty.class).changeApiToken();
        alice.getProperty(ApiTokenProperty.class).changeApiToken();

        // User can change only own token
        SecurityContextHolder.getContext().setAuthentication(bob.impersonate());
        bob.getProperty(ApiTokenProperty.class).changeApiToken();
        try {
            alice.getProperty(ApiTokenProperty.class).changeApiToken();
            fail("Bob should not be authorized to change alice's token");
        } catch (AccessDeniedException expected) { }

        // ANONYMOUS can not change any token
        SecurityContextHolder.getContext().setAuthentication(Jenkins.ANONYMOUS);
        try {
            alice.getProperty(ApiTokenProperty.class).changeApiToken();
            fail("Anonymous should not be authorized to change alice's token");
        } catch (AccessDeniedException expected) { }
    }

    @Issue("SECURITY-243")
    @Test
    public void resolveByIdThenName() throws Exception{
        j.jenkins.setSecurityRealm(new HudsonPrivateSecurityRealm(true, false, null));

        User u1 = User.get("user1");
        u1.setFullName("User One");
        u1.save();

        User u2 = User.get("user2");
        u2.setFullName("User Two");
        u2.save();

        assertNotSame("Users should not have the same id.", u1.getId(), u2.getId());

        User u = User.get("User One");
        assertEquals("'User One' should resolve to u1", u1.getId(), u.getId());

        u = User.get("User Two");
        assertEquals("'User Two' should resolve to u2", u2.getId(), u.getId());

        u = User.get("user1");
        assertEquals("'user1' should resolve to u1", u1.getId(), u.getId());

        u = User.get("user2");
        assertEquals("'user2' should resolve to u2", u2.getId(), u.getId());

        u1.setFullName("user2");
        u1.save();
        u = User.get("user2");
        assertEquals("'user2' should resolve to u2", u2.getId(), u.getId());
        u = User.get("user1");
        assertEquals("'user1' should resolve to u1", u1.getId(), u.getId());


        u1.setFullName("user1");
        u1.save();
        u2.setFullName("user1");
        u2.save();
        u = User.get("user1");
        assertEquals("'user1' should resolve to u1", u1.getId(), u.getId());
        u = User.get("user2");
        assertEquals("'user2' should resolve to u2", u2.getId(), u.getId());
    }

    @Issue("SECURITY-243")
    @Test
    public void resolveByUnloadedIdThenName() throws Exception {
        j.jenkins.setSecurityRealm(new ExternalSecurityRealm());
        // do *not* call this here: User.get("victim");
        User attacker1 = User.get("attacker1");
        attacker1.setFullName("victim1");
        User victim1 = User.get("victim1");
        assertEquals("victim1 is a real user ID, we must ignore the attacker1’s fullName", "victim1", victim1.getId());
        assertEquals("a recursive call to User.get was OK", null, victim1.getProperty(MyViewsProperty.class).getPrimaryViewName());
        assertEquals("(though the realm mistakenly added metadata to the attacker)", "victim1", attacker1.getProperty(MyViewsProperty.class).getPrimaryViewName());
        User.get("attacker2").setFullName("nonexistent");
        assertEquals("but if we cannot find such a user ID, allow the fullName", "attacker2", User.get("nonexistent").getId());
        User.get("attacker3").setFullName("unknown");
        assertEquals("or if we are not sure, allow the fullName", "attacker3", User.get("unknown").getId());
        User.get("attacker4").setFullName("Victim2");
        assertEquals("victim2 is a real (canonical) user ID", "victim2", User.get("Victim2").getId());

    }
    private static class ExternalSecurityRealm extends AbstractPasswordBasedSecurityRealm {
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            if (username.equals("nonexistent")) {
                throw new UsernameNotFoundException(username);
            } else if (username.equals("unknown")) {
                throw new UserMayOrMayNotExistException(username);
            } else {
                String canonicalName = username.toLowerCase(Locale.ENGLISH);
                try {
                    User.get(canonicalName).addProperty(new MyViewsProperty(canonicalName));
                } catch (IOException x) {
                    throw new RuntimeException(x);
                }
                return new org.acegisecurity.userdetails.User(canonicalName, "", true, true, true, true, new GrantedAuthority[] {AUTHENTICATED_AUTHORITY});
            }
        }
        @Override
        protected UserDetails authenticate(String username, String password) throws AuthenticationException {
            return loadUserByUsername(username); // irrelevant
        }
        @Override
        public GroupDetails loadGroupByGroupname(String groupname) throws UsernameNotFoundException {
            throw new UsernameNotFoundException(groupname); // irrelevant
        }
    }

    @Test
    public void resolveById() throws Exception {
        User u1 = User.get("user1");
        u1.setFullName("User One");
        u1.save();

        User u2 = User.get("user2");
        u2.setFullName("User Two");
        u2.save();

        assertNotSame("Users should not have the same id.", u1.getId(), u2.getId());

        // We can get the same user back.
        User u = User.getById("user1", false);
        assertSame("'user1' should return u1", u1, u);

        // passing true should not create a new user if it does not exist.
        u = User.getById("user1", true);
        assertSame("'user1' should return u1", u1, u);

        // should not lookup by name.
        u = User.getById("User One", false);
        assertNull("'User One' should not resolve to any user", u);

        // We can get the same user back.
        u = User.getById("user2", false);
        assertSame("'user2' should return u2", u2, u);

        // passing true should not create a new user if it does not exist.
        u = User.getById("user2", true);
        assertSame("'user2' should return u1", u2, u);

        // should not lookup by name.
        u = User.getById("User Two", false);
        assertNull("'User Two' should not resolve to any user", u);

        u1.setFullName("user1");
        u1.save();
        u2.setFullName("user1");
        u2.save();
        u = User.getById("user1", false);
        assertSame("'user1' should resolve to u1", u1, u);
        u = User.getById("user2", false);
        assertSame("'user2' should resolve to u2", u2, u);
    }

     public static class SomeUserProperty extends UserProperty {
         
        @TestExtension
        public static class DescriptorImpl extends UserPropertyDescriptor {
            @Override
            public UserProperty newInstance(User user) {
                return new SomeUserProperty();
            }
        }
    }

}
