/*
* 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 HudsonPrivateSecurityRealm(true, false, null){
@Override
public IdStrategy getUserIdStrategy() {
return 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 HudsonPrivateSecurityRealm(true, false, null){
@Override
public IdStrategy getUserIdStrategy() {
return 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 HudsonPrivateSecurityRealm(true, false, null){
@Override
public IdStrategy getUserIdStrategy() {
return 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())));
}
@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 contains 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 antoher 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 antoher 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();
}
}
}
}