/**********************************************************************************
*
* $Id: AuthenticatedUserProviderTest.java 105077 2012-02-24 22:54:29Z ottenhoff@longsight.com $
*
***********************************************************************************
*
* Copyright (c) 2007, 2008 Sakai Foundation
*
* Licensed under the Educational Community License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.opensource.org/licenses/ECL-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
**********************************************************************************/
package org.sakaiproject.user.impl.test;
import java.util.Collection;
import junit.extensions.TestSetup;
import junit.framework.Assert;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.authz.api.SecurityAdvisor;
import org.sakaiproject.authz.api.SecurityService;
import org.sakaiproject.test.SakaiKernelTestBase;
import org.sakaiproject.thread_local.api.ThreadLocalManager;
import org.sakaiproject.user.api.AuthenticatedUserProvider;
import org.sakaiproject.user.api.User;
import org.sakaiproject.user.api.UserDirectoryProvider;
import org.sakaiproject.user.api.UserDirectoryService;
import org.sakaiproject.user.api.UserEdit;
import org.sakaiproject.user.api.UserFactory;
import org.sakaiproject.user.api.UserNotDefinedException;
import org.sakaiproject.user.impl.DbUserService;
/**
*
*/
public class AuthenticatedUserProviderTest extends SakaiKernelTestBase {
protected static final String CONFIG = null;
private static Log log = LogFactory.getLog(AuthenticatedUserProviderTest.class);
private UserDirectoryService userDirectoryService;
private static TestProvider userDirectoryProvider;
// These services are only used to clear out various caches to make sure
// we're fetching from the DB.
private ThreadLocalManager threadLocalManager;
/**
* A complete integration test run is a lot of overhead to take on for
* such a small suite of tests. But since the tests rely on being set up with
* specially tailored providers, there's not much choice....
*
* @throws Exception
*/
public static Test suite() {
TestSetup setup = new TestSetup(new TestSuite(AuthenticatedUserProviderTest.class)) {
protected void setUp() throws Exception {
if (log.isDebugEnabled()) log.debug("starting setup");
try {
oneTimeSetup("disable_user_cache");
oneTimeSetupAfter();
} catch (Exception e) {
log.warn(e);
}
if (log.isDebugEnabled()) log.debug("finished setup");
}
protected void tearDown() throws Exception {
oneTimeTearDown();
}
};
return setup;
}
private static void oneTimeSetupAfter() throws Exception {
userDirectoryProvider = new TestProvider();
// This is a workaround until we can make it easier to load sakai.properties
// for specific integration tests.
DbUserService dbUserService = (DbUserService)getService(UserDirectoryService.class.getName());
dbUserService.setProvider(userDirectoryProvider);
// Inject service so that provider can create new user records or search
// for existing ones.
userDirectoryProvider.setUserFactory(dbUserService);
userDirectoryProvider.setUserDirectoryService(dbUserService);
userDirectoryProvider.setSecurityService((SecurityService)getService(SecurityService.class.getName()));
}
public void setUp() throws Exception {
log.debug("Setting up UserDirectoryServiceIntegrationTest");
userDirectoryService = (UserDirectoryService)getService(UserDirectoryService.class.getName());
threadLocalManager = (ThreadLocalManager)getService(ThreadLocalManager.class.getName());
}
public void testLocalUserFallThrough() throws Exception {
User user = userDirectoryService.addUser(null, "local", null, null, null, "localPW", null, null);
User authUser = userDirectoryService.authenticate("local", "localPW");
Assert.assertTrue(authUser.getId().equals(user.getId()));
}
public void testExistingProvidedUser() throws Exception {
User user = userDirectoryService.getUserByEid("provideduser");
Assert.assertTrue(user != null);
User authUser = userDirectoryService.authenticate("LOGINprovideduser", "provideduserPW");
Assert.assertTrue(authUser.getId().equals(user.getId()));
User failedUser = userDirectoryService.authenticate("LOGINprovideduser", "BadPassword");
Assert.assertTrue(failedUser == null);
}
public void testNewProvidedUsers() throws Exception {
User providedByAuthn = userDirectoryService.authenticate("LOGINprovidedauthn", "providedauthnPW");
Assert.assertTrue(providedByAuthn != null);
User providedByDelayedAuthn = userDirectoryService.authenticate("LOGINprovidernotfirst", "providernotfirstPW");
Assert.assertTrue(providedByDelayedAuthn != null);
User user = userDirectoryService.getUserByEid("providedauthn");
Assert.assertTrue(user.getId().equals(providedByAuthn.getId()));
user = userDirectoryService.getUserByEid("providernotfirst");
Assert.assertTrue(user.getId().equals(providedByDelayedAuthn.getId()));
}
public void testProviderCreatedAndUpdatedLocalUsers() throws Exception {
// Authenticate a user who will then magically appear in the Sakai user tables.
User providedByAuthn = userDirectoryService.authenticate("LOGINprovidercreated", "providercreatedPW");
String eid = providedByAuthn.getEid();
Assert.assertTrue(eid.equals("providercreated"));
String userId = providedByAuthn.getId();
// This is the tough part: make sure we get the new user from Sakai's user
// DB rather than any of the three caches.
clearUserFromServiceCaches(userId);
providedByAuthn = userDirectoryService.getUserByEid(eid);
Assert.assertTrue(providedByAuthn.getLastName().equals("Last Name, Sr."));
// Now authenticate the same user and make sure the Sakai-maintained
// user record was updated.
providedByAuthn = userDirectoryService.authenticate("LOGINprovidercreated", "providercreatedPW");
clearUserFromServiceCaches(userId);
providedByAuthn = userDirectoryService.getUserByEid(eid);
Assert.assertTrue(providedByAuthn.getLastName().equals("Last Name, Jr."));
}
/**
* WARNING: There seems to be NO easy way to reset the UserDirectoryService MemoryService-managed
* cache, and so the only way currently to test for real DB storage is to have this line
* in the "sakai.properties" file used for the test:
* cacheMinutes@org.sakaiproject.user.api.UserDirectoryService=0
*/
private void clearUserFromServiceCaches(String userId) {
((DbUserService)userDirectoryService).getIdEidCache().removeAll();
String ref = "/user/" + userId;
threadLocalManager.set(ref, null);
}
public static class TestProvider implements UserDirectoryProvider, AuthenticatedUserProvider {
private UserFactory userFactory;
private UserDirectoryService userDirectoryService;
private SecurityService securityService;
public boolean authenticateUser(String eid, UserEdit user, String password) {
// This should never be called since we implement the new interface.
throw new RuntimeException("authenticateUser unexpectedly called");
}
public boolean authenticateWithProviderFirst(String loginId) {
return !loginId.equals("LOGINprovidernotfirst");
}
public boolean findUserByEmail(UserEdit edit, String email) {
return false;
}
public boolean getUser(UserEdit user) {
String eid = user.getEid();
if (!eid.startsWith("provide")) {
return false;
} else if (eid.equals("providercreated")) {
// It's a little tricky to create a Sakai-stored user record from a
// provider. When Sakai doesn't find any record of the new EID,
// it will ask the provider for the corresponding user. If we
// obligingly fill in the data and hand it over, Sakai will continue
// to ask us for the provided user from then on. The only way
// for a provider to force local storage is to deny all
// knowledge of the user here and then add the Sakai user
// record later in the authentication logic.
return false;
} else {
return true;
}
}
@SuppressWarnings("unchecked")
public void getUsers(Collection users) {
}
public UserEdit getAuthenticatedUser(String loginId, String password) {
log.debug("getAuthenticatedUser " + loginId + ", " + password);
if (!loginId.startsWith("LOGINprovide")) return null;
String eid = loginId.substring("LOGIN".length());
if (password.equals(eid + "PW")) {
if (eid.equals("providercreated")) {
return createOrUpdateUserAfterAuthentication(eid, password);
} else {
// Thoroughly provided user.
UserEdit user = userFactory.newUser();
user.setEid(eid);
return user;
}
} else {
return null;
}
}
/**
* Sakai framework APIs don't always clearly distinguish between internal core utility services
* (which just do their specialized job) and application-facing support services (which
* require authorization checks). UserDirectoryService's user record modification methods
* were originally written for application support, and so they check the permissions of the
* current user. During the authentication process, there is no current user, and so
* if we want to modify the Sakai-stored user record, we need to push a SecurityAdvisor
* to bypass the checks.
*/
private UserEdit createOrUpdateUserAfterAuthentication(String eid, String password) {
// User record is created by provider but stored by core Sakai.
UserEdit user = null;
try {
user = (UserEdit)userDirectoryService.getUserByEid(eid);
try {
securityService.pushAdvisor(new SecurityAdvisor() {
public SecurityAdvice isAllowed(String userId, String function, String reference) {
if (function.equals(UserDirectoryService.SECURE_UPDATE_USER_ANY)) {
return SecurityAdvice.ALLOWED;
} else {
return SecurityAdvice.NOT_ALLOWED;
}
}
});
user = userDirectoryService.editUser(user.getId());
user.setLastName("Last Name, Jr.");
userDirectoryService.commitEdit(user);
} catch (Exception e) {
log.warn(e);
} finally {
securityService.popAdvisor();
}
} catch (UserNotDefinedException e) {
try {
securityService.pushAdvisor(new SecurityAdvisor() {
public SecurityAdvice isAllowed(String userId, String function, String reference) {
if (function.equals(UserDirectoryService.SECURE_ADD_USER)) {
return SecurityAdvice.ALLOWED;
} else {
return SecurityAdvice.NOT_ALLOWED;
}
}
});
user = (UserEdit)userDirectoryService.addUser(null, eid, "First", "Last Name, Sr.", "eid@somewhere.edu", password, "Student", null);
} catch (Exception e1) {
log.warn(e1);
} finally {
securityService.popAdvisor();
}
}
return user;
}
public void setUserFactory(UserFactory userFactory) {
this.userFactory = userFactory;
}
public void setUserDirectoryService(UserDirectoryService userDirectoryService) {
this.userDirectoryService = userDirectoryService;
}
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
}
}