Index: lams_admin/conf/language/lams/ApplicationResources.properties =================================================================== diff -u -r7a038b413299c10aa5b3dd168e35811597f44173 -rab19db088359a46353cc92e806c40ff5cff818b9 --- lams_admin/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision 7a038b413299c10aa5b3dd168e35811597f44173) +++ lams_admin/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision ab19db088359a46353cc92e806c40ff5cff818b9) @@ -441,4 +441,9 @@ message.check.to.add.monitor = Check the box of each monitor to add as staff to each of the new lessons. label.return.to.group = Return to group +admin.openid.title =OpenID Settings +admin.openid.enabled =Enable OpenID logins +admin.openid.portalurl =Portal URL +admin.openid.trustedidps = Trusted identity providers (Comma separated) + #======= End labels: Exported 409 labels for en AU ===== Index: lams_admin/conf/language/lams/ApplicationResources_en_AU.properties =================================================================== diff -u -r7a038b413299c10aa5b3dd168e35811597f44173 -rab19db088359a46353cc92e806c40ff5cff818b9 --- lams_admin/conf/language/lams/ApplicationResources_en_AU.properties (.../ApplicationResources_en_AU.properties) (revision 7a038b413299c10aa5b3dd168e35811597f44173) +++ lams_admin/conf/language/lams/ApplicationResources_en_AU.properties (.../ApplicationResources_en_AU.properties) (revision ab19db088359a46353cc92e806c40ff5cff818b9) @@ -439,4 +439,9 @@ message.check.to.add.monitor = Check the box of each monitor to add as staff to each of the new lessons. label.return.to.group = Return to group +admin.openid.title =OpenID Settings +admin.openid.enabled =Enable OpenID logins +admin.openid.portalurl =Portal URL +admin.openid.trustedidps = Trusted identity providers (Comma separated) + #======= End labels: Exported 409 labels for en AU ===== Index: lams_admin/src/java/org/lamsfoundation/lams/admin/web/OpenIDConfigAction.java =================================================================== diff -u --- lams_admin/src/java/org/lamsfoundation/lams/admin/web/OpenIDConfigAction.java (revision 0) +++ lams_admin/src/java/org/lamsfoundation/lams/admin/web/OpenIDConfigAction.java (revision ab19db088359a46353cc92e806c40ff5cff818b9) @@ -0,0 +1,112 @@ +/**************************************************************** + * Copyright (C) 2006 LAMS Foundation (http://lamsfoundation.org) + * ============================================================= + * License Information: http://lamsfoundation.org/licensing/lams/2.0/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2.0 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + * + * http://www.gnu.org/licenses/gpl.txt + * **************************************************************** + */ +package org.lamsfoundation.lams.admin.web; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.struts.action.ActionForm; +import org.apache.struts.action.ActionForward; +import org.apache.struts.action.ActionMapping; +import org.lamsfoundation.lams.admin.service.AdminServiceProxy; +import org.lamsfoundation.lams.openid.OpenIDConfig; +import org.lamsfoundation.lams.usermanagement.service.IUserManagementService; +import org.lamsfoundation.lams.web.action.LamsDispatchAction; + +/** + * openIDConfig + * + * @author lfoxton + * + * @struts.action path="/openIDConfig" parameter="method" name="openIDForm" scope="request" validate="false" + * @struts.action-forward name="config" path="/openidConfig.jsp" + * @struts.action-forward name="sysadmin" path="/sysadminstart.do" + */ +public class OpenIDConfigAction extends LamsDispatchAction { + + IUserManagementService userService; + + public ActionForward unspecified(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws Exception { + + setService(); + OpenIDConfigForm configForm = (OpenIDConfigForm) form; + + OpenIDConfig openIDEnabled = (OpenIDConfig)userService.findById(OpenIDConfig.class, OpenIDConfig.KEY_ENABLED); + OpenIDConfig portalURL = (OpenIDConfig)userService.findById(OpenIDConfig.class, OpenIDConfig.KEY_PORTAL_URL); + OpenIDConfig trustedIDPs = (OpenIDConfig)userService.findById(OpenIDConfig.class,OpenIDConfig.KEY_TRUSTED_IDPS); + + if (openIDEnabled != null) { + configForm.setOpenIDEnabled(Boolean.parseBoolean(openIDEnabled.getConfigValue())); + } else { + configForm.setOpenIDEnabled(Boolean.FALSE); + } + + if (portalURL != null) { + configForm.setPortalURL(portalURL.getConfigValue()); + } + + if (trustedIDPs != null) { + configForm.setTrustedIDPs(trustedIDPs.getConfigValue()); + } + + return mapping.findForward("config"); + } + + public ActionForward save(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws Exception { + setService(); + OpenIDConfigForm configForm = (OpenIDConfigForm) form; + + OpenIDConfig openIDEnabled = (OpenIDConfig)userService.findById(OpenIDConfig.class, OpenIDConfig.KEY_ENABLED); + OpenIDConfig portalURL = (OpenIDConfig)userService.findById(OpenIDConfig.class, OpenIDConfig.KEY_PORTAL_URL); + OpenIDConfig trustedIDPs = (OpenIDConfig)userService.findById(OpenIDConfig.class,OpenIDConfig.KEY_TRUSTED_IDPS); + + if (openIDEnabled != null) { + openIDEnabled.setConfigValue(configForm.getOpenIDEnabled().toString()); + } + + if (portalURL != null) { + portalURL.setConfigValue(configForm.getPortalURL()); + } + + if (trustedIDPs != null) { + trustedIDPs.setConfigValue(configForm.getTrustedIDPs()); + } + + userService.save(openIDEnabled); + userService.save(portalURL); + userService.save(trustedIDPs); + + request.setAttribute("success", true); + + return mapping.findForward("config"); + } + + private void setService() { + if (userService == null) { + userService = AdminServiceProxy.getService(getServlet().getServletContext()); + } + } + +} Index: lams_admin/src/java/org/lamsfoundation/lams/admin/web/OpenIDConfigForm.java =================================================================== diff -u --- lams_admin/src/java/org/lamsfoundation/lams/admin/web/OpenIDConfigForm.java (revision 0) +++ lams_admin/src/java/org/lamsfoundation/lams/admin/web/OpenIDConfigForm.java (revision ab19db088359a46353cc92e806c40ff5cff818b9) @@ -0,0 +1,43 @@ +package org.lamsfoundation.lams.admin.web; + +import org.apache.struts.action.ActionForm; + +/** + * + * @author lfoxton + * @struts.form name="openIDForm" + */ +public class OpenIDConfigForm extends ActionForm { + + private static final long serialVersionUID = 1453453453463790L; + + private Boolean openIDEnabled; + private String portalURL; + private String trustedIDPs; + + public OpenIDConfigForm() {} + + public Boolean getOpenIDEnabled() { + return openIDEnabled; + } + + public void setOpenIDEnabled(Boolean openIDEnabled) { + this.openIDEnabled = openIDEnabled; + } + + public String getPortalURL() { + return portalURL; + } + + public void setPortalURL(String portalURL) { + this.portalURL = portalURL; + } + + public String getTrustedIDPs() { + return trustedIDPs; + } + + public void setTrustedIDPs(String trustedIDPs) { + this.trustedIDPs = trustedIDPs; + } +} Index: lams_admin/src/java/org/lamsfoundation/lams/admin/web/action/SysAdminStartAction.java =================================================================== diff -u -r4abf8ee86bc60a0b94edfdc4c907c28c6c5df13e -rab19db088359a46353cc92e806c40ff5cff818b9 --- lams_admin/src/java/org/lamsfoundation/lams/admin/web/action/SysAdminStartAction.java (.../SysAdminStartAction.java) (revision 4abf8ee86bc60a0b94edfdc4c907c28c6c5df13e) +++ lams_admin/src/java/org/lamsfoundation/lams/admin/web/action/SysAdminStartAction.java (.../SysAdminStartAction.java) (revision ab19db088359a46353cc92e806c40ff5cff818b9) @@ -34,6 +34,7 @@ import org.apache.struts.action.ActionMapping; import org.lamsfoundation.lams.admin.service.AdminServiceProxy; import org.lamsfoundation.lams.admin.web.dto.LinkBean; +import org.lamsfoundation.lams.openid.OpenIDConfig; import org.lamsfoundation.lams.usermanagement.Role; import org.lamsfoundation.lams.usermanagement.service.IUserManagementService; @@ -73,6 +74,11 @@ links.add(new LinkBean("libraryManage.do", "sysadmin.library.management")); links.add(new LinkBean("statistics.do", "admin.statistics.title")); links.add(new LinkBean("themeManagement.do", "admin.themes.title")); + + OpenIDConfig openIDEnabled = (OpenIDConfig)service.findById(OpenIDConfig.class, OpenIDConfig.KEY_ENABLED); + if (openIDEnabled != null && Boolean.parseBoolean(openIDEnabled.getConfigValue()) == Boolean.TRUE) { + links.add(new LinkBean("openIDConfig.do", "admin.openid.title")); + } } else if (request.isUserInRole(Role.AUTHOR_ADMIN)) { LinkBean linkBean = new LinkBean("toolcontentlist.do", "sysadmin.tool.management"); links.add(linkBean); Index: lams_admin/web/WEB-INF/struts/struts-config.xml =================================================================== diff -u -r51767af10de37c113c0c7b5c0b793380bf2729a6 -rab19db088359a46353cc92e806c40ff5cff818b9 --- lams_admin/web/WEB-INF/struts/struts-config.xml (.../struts-config.xml) (revision 51767af10de37c113c0c7b5c0b793380bf2729a6) +++ lams_admin/web/WEB-INF/struts/struts-config.xml (.../struts-config.xml) (revision ab19db088359a46353cc92e806c40ff5cff818b9) @@ -22,10 +22,6 @@ type="org.lamsfoundation.lams.admin.web.RegisterForm" /> - @@ -34,17 +30,25 @@ type="org.lamsfoundation.lams.admin.web.form.ImportV1ContentsForm" /> - + + + @@ -165,110 +169,233 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + - - - Index: lams_admin/web/openidConfig.jsp =================================================================== diff -u --- lams_admin/web/openidConfig.jsp (revision 0) +++ lams_admin/web/openidConfig.jsp (revision ab19db088359a46353cc92e806c40ff5cff818b9) @@ -0,0 +1,60 @@ + +<%@ include file="/taglibs.jsp"%> + + + <fmt:message key="admin.openid.title" /> + + + + + +
+

+

+ +

+

+ +

+ +

+ + + + + + + + + + + + + + + + + +
+ +
+ +
+ +
+ + + + +
+ +

+ + + + + + + + \ No newline at end of file Index: lams_build/3rdParty.userlibraries =================================================================== diff -u -r5fbacc92f77184afd0cf520150274aa4d38df434 -rab19db088359a46353cc92e806c40ff5cff818b9 --- lams_build/3rdParty.userlibraries (.../3rdParty.userlibraries) (revision 5fbacc92f77184afd0cf520150274aa4d38df434) +++ lams_build/3rdParty.userlibraries (.../3rdParty.userlibraries) (revision ab19db088359a46353cc92e806c40ff5cff818b9) @@ -105,5 +105,8 @@ + + + Index: lams_build/build.xml =================================================================== diff -u -r094b2833bd38dfc4e0e9f453b72352728acb727a -rab19db088359a46353cc92e806c40ff5cff818b9 --- lams_build/build.xml (.../build.xml) (revision 094b2833bd38dfc4e0e9f453b72352728acb727a) +++ lams_build/build.xml (.../build.xml) (revision ab19db088359a46353cc92e806c40ff5cff818b9) @@ -138,6 +138,7 @@ + Index: lams_build/lib/jakarta-commons/commons-httpclient-3.0.jar =================================================================== diff -u Binary files differ Index: lams_build/lib/joid/joid.jar =================================================================== diff -u Binary files differ Index: lams_build/lib/joid/tsik.jar =================================================================== diff -u Binary files differ Index: lams_build/lib/lams/lams.jar =================================================================== diff -u -rd37b107bce1936b6c077dac9002dffd29af5343a -rab19db088359a46353cc92e806c40ff5cff818b9 Binary files differ Index: lams_central/conf/xdoclet/filter-mappings.xml =================================================================== diff -u -rf4ca162214e069dde32ea9f32a4c82baaf83b3a4 -rab19db088359a46353cc92e806c40ff5cff818b9 --- lams_central/conf/xdoclet/filter-mappings.xml (.../filter-mappings.xml) (revision f4ca162214e069dde32ea9f32a4c82baaf83b3a4) +++ lams_central/conf/xdoclet/filter-mappings.xml (.../filter-mappings.xml) (revision ab19db088359a46353cc92e806c40ff5cff818b9) @@ -1,109 +1,119 @@ - - SystemSessionFilter - *.do - - - - SystemSessionFilter - *.jsp - - - - SystemSessionFilter - *.xml - - - - SystemSessionFilter - *.swf - - - - SystemSessionFilter - /servlet/* - - - - SystemSessionFilter - /fckeditor/* - - - - SystemSessionFilter - /eof/* - - - - hibernateFilter - *.do - - - - hibernateFilter - *.jsp - - - - hibernateFilter - *.xml - - - - hibernateFilter - *.swf - - - - hibernateFilter - /servlet/* - - - - hibernateFilter - /services/* - + + SystemSessionFilter + *.do + - - hibernateFilter - /fckeditor/* - + + SystemSessionFilter + *.jsp + - + + SystemSessionFilter + *.xml + + + + SystemSessionFilter + *.swf + + + + SystemSessionFilter + /servlet/* + + + + SystemSessionFilter + /fckeditor/* + + + + SystemSessionFilter + /eof/* + + + + hibernateFilter + *.do + + + + hibernateFilter + *.jsp + + + + hibernateFilter + *.xml + + + + hibernateFilter + *.swf + + + + hibernateFilter + /servlet/* + + + + hibernateFilter + /services/* + + + + hibernateFilter + /fckeditor/* + + + + hibernateFilter + /LoginRequest + + + + hibernateFilter + /ForgotPasswordRequest + + + + LocaleFilter + *.do + + + + LocaleFilter + *.jsp + + + + LocaleFilter + *.xml + + + + LocaleFilter + *.swf + + + + LocaleFilter + /servlet/* + + + + LocaleFilter + /fckeditor/* + + + + OpenIdFilter +/* + + + hibernateFilter - /LoginRequest - - - - hibernateFilter - /ForgotPasswordRequest - - - - LocaleFilter - *.do - - - - LocaleFilter - *.jsp - - - - LocaleFilter - *.xml - - - - LocaleFilter - *.swf - - - - LocaleFilter - /servlet/* - - - - LocaleFilter - /fckeditor/* - + /OpenIDServlet + Index: lams_central/conf/xdoclet/filters.xml =================================================================== diff -u -r4819c022d38c1e327a48f7f9961496f3366cfa29 -rab19db088359a46353cc92e806c40ff5cff818b9 --- lams_central/conf/xdoclet/filters.xml (.../filters.xml) (revision 4819c022d38c1e327a48f7f9961496f3366cfa29) +++ lams_central/conf/xdoclet/filters.xml (.../filters.xml) (revision ab19db088359a46353cc92e806c40ff5cff818b9) @@ -1,24 +1,44 @@ - - SystemSessionFilter - - org.lamsfoundation.lams.web.session.SystemSessionFilter - - - - - hibernateFilter - - org.lamsfoundation.lams.util.CustomizedOpenSessionInViewFilter - + + SystemSessionFilter + + org.lamsfoundation.lams.web.session.SystemSessionFilter + + + + + hibernateFilter + + org.lamsfoundation.lams.util.CustomizedOpenSessionInViewFilter + sessionFactoryBeanName coreSessionFactory - - - - LocaleFilter - - org.lamsfoundation.lams.web.filter.LocaleFilter - - \ No newline at end of file + + + + LocaleFilter + + org.lamsfoundation.lams.web.filter.LocaleFilter + + + + + OpenIdFilter + org.verisign.joid.consumer.OpenIdFilter + + saveInCookie + true + + + + ignorePaths + /login,/server,/echo + + \ No newline at end of file Index: lams_central/conf/xdoclet/servlet-mappings.xml =================================================================== diff -u -r4b24094f139dd10a9779ce6e678cb8ca38317148 -rab19db088359a46353cc92e806c40ff5cff818b9 --- lams_central/conf/xdoclet/servlet-mappings.xml (.../servlet-mappings.xml) (revision 4b24094f139dd10a9779ce6e678cb8ca38317148) +++ lams_central/conf/xdoclet/servlet-mappings.xml (.../servlet-mappings.xml) (revision ab19db088359a46353cc92e806c40ff5cff818b9) @@ -41,4 +41,9 @@ JabberHTTPBindingServlet /JHB/* + + + + OpenIDServlet + /OpenIDServlet \ No newline at end of file Index: lams_central/conf/xdoclet/servlets.xml =================================================================== diff -u -r6ea112b9b57636ff8cf428beaf3e91df321aa0d9 -rab19db088359a46353cc92e806c40ff5cff818b9 --- lams_central/conf/xdoclet/servlets.xml (.../servlets.xml) (revision 6ea112b9b57636ff8cf428beaf3e91df321aa0d9) +++ lams_central/conf/xdoclet/servlets.xml (.../servlets.xml) (revision ab19db088359a46353cc92e806c40ff5cff818b9) @@ -82,4 +82,11 @@ org.jabber.JabberHTTPBind.JHBServlet - + + OpenIDServlet + + org.lamsfoundation.lams.web.SIFOpenIDServlet + + + + Index: lams_central/src/java/org/lamsfoundation/lams/security/UniversalLoginModule.java =================================================================== diff -u -r7998a31f6d7d4f1eef3e866bafef22caa012eb7a -rab19db088359a46353cc92e806c40ff5cff818b9 --- lams_central/src/java/org/lamsfoundation/lams/security/UniversalLoginModule.java (.../UniversalLoginModule.java) (revision 7998a31f6d7d4f1eef3e866bafef22caa012eb7a) +++ lams_central/src/java/org/lamsfoundation/lams/security/UniversalLoginModule.java (.../UniversalLoginModule.java) (revision ab19db088359a46353cc92e806c40ff5cff818b9) @@ -24,12 +24,12 @@ package org.lamsfoundation.lams.security; /** - * UniversalLoginModule is LAMS's own implementation of login module - * based on JBoss 3.0.*, 3.2.* and possibly higher versions. - * - * It's named "universal" as currently it supports WebAuth, LDAP and - * database based authentication mechanisms. - * + * UniversalLoginModule is LAMS's own implementation of login module based on + * JBoss 3.0.*, 3.2.* and possibly higher versions. + * + * It's named "universal" as currently it supports WebAuth, LDAP and database + * based authentication mechanisms. + * */ import java.security.Principal; @@ -75,309 +75,340 @@ public class UniversalLoginModule extends UsernamePasswordLoginModule { - private static Logger log = Logger.getLogger(UniversalLoginModule.class); + private static Logger log = Logger.getLogger(UniversalLoginModule.class); - public UniversalLoginModule() { - } + public UniversalLoginModule() { + } - protected String dsJndiName; + protected String dsJndiName; - protected String rolesQuery; + protected String rolesQuery; - protected String principalsQuery; + protected String principalsQuery; - private IThemeService themeService; - private UserManagementService service; + private IThemeService themeService; + private UserManagementService service; - public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { - super.initialize(subject, callbackHandler, sharedState, options); - dsJndiName = (String) options.get("dsJndiName"); - principalsQuery = (String) options.get("principalsQuery"); - rolesQuery = (String) options.get("rolesQuery"); - } + public void initialize(Subject subject, CallbackHandler callbackHandler, + Map sharedState, Map options) { + super.initialize(subject, callbackHandler, sharedState, options); + dsJndiName = (String) options.get("dsJndiName"); + principalsQuery = (String) options.get("principalsQuery"); + rolesQuery = (String) options.get("rolesQuery"); + } - protected boolean validatePassword(String inputPassword, String expectedPassword) { - boolean isValid = false; - if (inputPassword != null) { - // empty password not allowed - if (inputPassword.length() == 0) - return false; + protected boolean validatePassword(String inputPassword, + String expectedPassword) { + boolean isValid = false; + if (inputPassword != null) { + // empty password not allowed + if (inputPassword.length() == 0) + return false; - try { - String username = getUsername(); - log.debug("===> authenticating user: " + username); + try { + String username = getUsername(); + log.debug("===> authenticating user: " + username); - WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(HttpSessionManager - .getInstance().getServletContext()); + WebApplicationContext ctx = WebApplicationContextUtils + .getWebApplicationContext(HttpSessionManager + .getInstance().getServletContext()); - if (service == null) { - service = (UserManagementService) ctx.getBean("userManagementService"); - } - User user = service.getUserByLogin(username); + if (service == null) { + service = (UserManagementService) ctx + .getBean("userManagementService"); + } + User user = service.getUserByLogin(username); - if (themeService == null) { - themeService = (IThemeService) ctx.getBean("themeService"); - } + if (themeService == null) { + themeService = (IThemeService) ctx.getBean("themeService"); + } - // LDAP user provisioning - if (user == null) { - // provision a new user by checking ldap server - if (Configuration.getAsBoolean(ConfigurationKeys.LDAP_PROVISIONING_ENABLED)) { - LdapService ldapService; - try { - ldapService = (LdapService) ctx.getBean("ldapService"); - } catch (NoSuchBeanDefinitionException e) { - // LDEV-1937 - log - .error( - "NoSuchBeanDefinitionException while getting ldapService bean, will try another method...", - e); - ApplicationContext context = new ClassPathXmlApplicationContext( - "org/lamsfoundation/lams/usermanagement/ldapContext.xml"); - ldapService = (LdapService) context.getBean("ldapService"); - } - log.debug("===> LDAP provisioning is enabled, checking username against LDAP server..."); - LDAPAuthenticator ldap = new LDAPAuthenticator(); - isValid = ldap.authenticate(username, inputPassword); - if (isValid) { // create a new user - log.info("===> Creating new user for LDAP username: " + username); - if (ldapService.createLDAPUser(ldap.getAttrs())) { - user = service.getUserByLogin(username); - if (!ldapService.addLDAPUser(ldap.getAttrs(), user.getUserId())) { - log.error("===> Couldn't add LDAP user: " + username + " to organisation."); + // LDAP user provisioning + if (user == null) { + // provision a new user by checking ldap server + if (Configuration + .getAsBoolean(ConfigurationKeys.LDAP_PROVISIONING_ENABLED)) { + LdapService ldapService; + try { + ldapService = (LdapService) ctx + .getBean("ldapService"); + } catch (NoSuchBeanDefinitionException e) { + // LDEV-1937 + log + .error( + "NoSuchBeanDefinitionException while getting ldapService bean, will try another method...", + e); + ApplicationContext context = new ClassPathXmlApplicationContext( + "org/lamsfoundation/lams/usermanagement/ldapContext.xml"); + ldapService = (LdapService) context + .getBean("ldapService"); + } + log + .debug("===> LDAP provisioning is enabled, checking username against LDAP server..."); + LDAPAuthenticator ldap = new LDAPAuthenticator(); + isValid = ldap.authenticate(username, inputPassword); + if (isValid) { // create a new user + log + .info("===> Creating new user for LDAP username: " + + username); + if (ldapService.createLDAPUser(ldap.getAttrs())) { + user = service.getUserByLogin(username); + if (!ldapService.addLDAPUser(ldap.getAttrs(), + user.getUserId())) { + log.error("===> Couldn't add LDAP user: " + + username + " to organisation."); + } + } else { + log + .error("===> Couldn't create new user for LDAP username: " + + username); + return false; + } + } else { // didn't authenticate successfully with + // ldap + return false; + } + } else { + return false; + } } - } else { - log.error("===> Couldn't create new user for LDAP username: " + username); - return false; - } - } else { // didn't authenticate successfully with ldap - return false; - } - } else { - return false; - } - } - // allow sysadmin to login as another user; in this case, the - // LAMS shared session - // will be present, allowing the following check to work - if (service.isUserSysAdmin()) { - isValid = true; - } + // allow sysadmin to login as another user; in this case, the + // LAMS shared session + // will be present, allowing the following check to work + if (service.isUserSysAdmin()) { + isValid = true; + } - // perform password checking according to user's authentication - // method - if (!isValid) { - String type = user.getAuthenticationMethod().getAuthenticationMethodType().getDescription(); - log.debug("===> authentication type: " + type); - if (AuthenticationMethodType.LDAP.equals(type)) { - LDAPAuthenticator authenticator = new LDAPAuthenticator(); - isValid = authenticator.authenticate(username, inputPassword); - // if ldap user profile has updated, udpate user object - // for dto below - user = service.getUserByLogin(username); - } else if (AuthenticationMethodType.LAMS.equals(type)) { - DatabaseAuthenticator authenticator = new DatabaseAuthenticator(dsJndiName, principalsQuery); - // if the password is not encrypted when sent from the - // jsp (e.g. when it is passed - // unencrypted to say, ldap) then encrypt it here when - // authenticating against local db - if (!Configuration.getAsBoolean(ConfigurationKeys.LDAP_ENCRYPT_PASSWORD_FROM_BROWSER)) { - // try the passed in password first, - // LoginRequestServlet always passes in encrypted - // passwords - isValid = authenticator.authenticate(username, inputPassword); - if (!isValid) { - inputPassword = HashUtil.sha1(inputPassword); - } - isValid = authenticator.authenticate(username, inputPassword); - } else { - isValid = authenticator.authenticate(username, inputPassword); - } - } else if (AuthenticationMethodType.WEB_AUTH.equals(type)) { - WebAuthAuthenticator authenticator = new WebAuthAuthenticator(); - isValid = authenticator.authenticate(username, inputPassword); - } else { - log.error("===> Unexpected authentication type: " + type); - return false; - } - } + // perform password checking according to user's authentication + // method + if (!isValid) { + String type = user.getAuthenticationMethod() + .getAuthenticationMethodType().getDescription(); + log.debug("===> authentication type: " + type); + if (AuthenticationMethodType.LDAP.equals(type)) { + LDAPAuthenticator authenticator = new LDAPAuthenticator(); + isValid = authenticator.authenticate(username, + inputPassword); + // if ldap user profile has updated, udpate user object + // for dto below + user = service.getUserByLogin(username); + } else if (AuthenticationMethodType.LAMS.equals(type)) { + DatabaseAuthenticator authenticator = new DatabaseAuthenticator( + dsJndiName, principalsQuery); + // if the password is not encrypted when sent from the + // jsp (e.g. when it is passed + // unencrypted to say, ldap) then encrypt it here when + // authenticating against local db + if (!Configuration + .getAsBoolean(ConfigurationKeys.LDAP_ENCRYPT_PASSWORD_FROM_BROWSER)) { + // try the passed in password first, + // LoginRequestServlet always passes in encrypted + // passwords + isValid = authenticator.authenticate(username, + inputPassword); + if (!isValid) { + inputPassword = HashUtil.sha1(inputPassword); + } + isValid = authenticator.authenticate(username, + inputPassword); + } else { + isValid = authenticator.authenticate(username, + inputPassword); + } + } else if (AuthenticationMethodType.WEB_AUTH.equals(type)) { + WebAuthAuthenticator authenticator = new WebAuthAuthenticator(); + isValid = authenticator.authenticate(username, + inputPassword); + } else { + log.error("===> Unexpected authentication type: " + + type); + return false; + } + } - // disabled users can't login; - // check after authentication to give non-db authentication - // methods - // a chance to update disabled flag - if (user.getDisabledFlag()) { - log.debug("===> user is disabled."); - return false; - } + // disabled users can't login; + // check after authentication to give non-db authentication + // methods + // a chance to update disabled flag + if (user.getDisabledFlag()) { + log.debug("===> user is disabled."); + return false; + } - // if login is valid, register userDTO into session. - if (isValid) { - UserDTO userDTO = user.getUserDTO(); + // if login is valid, register userDTO into session. + if (isValid) { + UserDTO userDTO = user.getUserDTO(); - // If the user's css theme has been deleted, use the default as fallback - CSSThemeBriefDTO userCSSTheme = userDTO.getHtmlTheme(); - if (userCSSTheme != null) { - boolean themeExists = false; - for (Theme theme : themeService.getAllCSSThemes()) { - if (userCSSTheme.getId().equals(theme.getThemeId())) { - themeExists = true; - break; - } - } + // If the user's css theme has been deleted, use the default + // as fallback + CSSThemeBriefDTO userCSSTheme = userDTO.getHtmlTheme(); + if (userCSSTheme != null) { + boolean themeExists = false; + for (Theme theme : themeService.getAllCSSThemes()) { + if (userCSSTheme.getId().equals(theme.getThemeId())) { + themeExists = true; + break; + } + } - if (!themeExists) { - userDTO.setHtmlTheme(new CSSThemeBriefDTO(themeService.getDefaultCSSTheme())); - } - } - - // If the user's flash theme has been deleted, use the default as fallback - CSSThemeBriefDTO userFlashTheme = userDTO.getFlashTheme(); - if (userFlashTheme != null) { - boolean themeExists = false; - for (Theme theme : themeService.getAllFlashThemes()) { - if (userFlashTheme.getId().equals(theme.getThemeId())) { - themeExists = true; - break; - } - } + if (!themeExists) { + userDTO.setHtmlTheme(new CSSThemeBriefDTO( + themeService.getDefaultCSSTheme())); + } + } - if (!themeExists) { - userDTO.setFlashTheme(new CSSThemeBriefDTO(themeService.getDefaultFlashTheme())); - } - } + // If the user's flash theme has been deleted, use the + // default as fallback + CSSThemeBriefDTO userFlashTheme = userDTO.getFlashTheme(); + if (userFlashTheme != null) { + boolean themeExists = false; + for (Theme theme : themeService.getAllFlashThemes()) { + if (userFlashTheme.getId().equals( + theme.getThemeId())) { + themeExists = true; + break; + } + } - HttpSession sharedsession = SessionManager.getSession(); - sharedsession.setAttribute(AttributeNames.USER, userDTO); + if (!themeExists) { + userDTO.setFlashTheme(new CSSThemeBriefDTO( + themeService.getDefaultFlashTheme())); + } + } + + HttpSession sharedsession = SessionManager.getSession(); + sharedsession.setAttribute(AttributeNames.USER, userDTO); + } + } catch (Exception e) { + e.printStackTrace(); + log.error("===> exception: " + e, e); + } } - } catch (Exception e) { - e.printStackTrace(); - log.error("===> exception: " + e, e); - } + return isValid; } - return isValid; - } - /** - * According to Lams's security policy, all the authorization must be done - * locally, in other word, through Lams database or other "local"(logically) - * data resource. - * - * @return Group[] containing the sets of roles - */ - protected Group[] getRoleSets() throws LoginException { - String username = getUsername(); - Connection conn = null; - HashMap setsMap = new HashMap(); - PreparedStatement ps = null; - ResultSet rs = null; + /** + * According to Lams's security policy, all the authorization must be done + * locally, in other word, through Lams database or other "local"(logically) + * data resource. + * + * @return Group[] containing the sets of roles + */ + protected Group[] getRoleSets() throws LoginException { + String username = getUsername(); + Connection conn = null; + HashMap setsMap = new HashMap(); + PreparedStatement ps = null; + ResultSet rs = null; - try { + try { - InitialContext ctx = new InitialContext(); - DataSource ds = (DataSource) ctx.lookup(this.dsJndiName); + InitialContext ctx = new InitialContext(); + DataSource ds = (DataSource) ctx.lookup(this.dsJndiName); - // log.debug("===> getRoleSets() called: " + dsJndiName + ": " + - // rolesQuery); - conn = ds.getConnection(); - // Get the user role names - ps = conn.prepareStatement(this.rolesQuery); - try { - ps.setString(1, username); - } catch (ArrayIndexOutOfBoundsException ignore) { - // The query may not have any parameters so just try it - } - rs = ps.executeQuery(); - if (rs.next() == false) { - if (getUnauthenticatedIdentity() == null) - throw new FailedLoginException("No matching username found in Roles"); - /* - * We are running with an unauthenticatedIdentity so create an - * empty Roles set and return. - */ - Group[] roleSets = { new SimpleGroup("Roles") }; - return roleSets; - } + // log.debug("===> getRoleSets() called: " + dsJndiName + ": " + + // rolesQuery); + conn = ds.getConnection(); + // Get the user role names + ps = conn.prepareStatement(this.rolesQuery); + try { + ps.setString(1, username); + } catch (ArrayIndexOutOfBoundsException ignore) { + // The query may not have any parameters so just try it + } + rs = ps.executeQuery(); + if (rs.next() == false) { + if (getUnauthenticatedIdentity() == null) + throw new FailedLoginException( + "No matching username found in Roles"); + /* + * We are running with an unauthenticatedIdentity so create an + * empty Roles set and return. + */ + Group[] roleSets = { new SimpleGroup("Roles") }; + return roleSets; + } - ArrayList groupMembers = new ArrayList(); - do { - String name = rs.getString(1); - String groupName = rs.getString(2); - if (groupName == null || groupName.length() == 0) - groupName = "Roles"; - Group group = (Group) setsMap.get(groupName); - if (group == null) { - group = new SimpleGroup(groupName); - setsMap.put(groupName, group); - } + ArrayList groupMembers = new ArrayList(); + do { + String name = rs.getString(1); + String groupName = rs.getString(2); + if (groupName == null || groupName.length() == 0) + groupName = "Roles"; + Group group = (Group) setsMap.get(groupName); + if (group == null) { + group = new SimpleGroup(groupName); + setsMap.put(groupName, group); + } - try { - Principal p; - // Assign minimal role if user has none - if (name == null) { - name = Role.LEARNER; - log.info("===> Found no roles"); - } - p = super.createIdentity(name); - if (!groupMembers.contains(name)) { - log.info("===> Assign user to role " + p.getName()); - group.addMember(p); - groupMembers.add(name); - } - if (name.equals(Role.SYSADMIN) || name.equals(Role.AUTHOR_ADMIN)) { - p = super.createIdentity(Role.AUTHOR); - log.info("===> Found " + name); - if (!groupMembers.contains(Role.AUTHOR)) { - log.info("===> Assign user to role " + Role.AUTHOR); - group.addMember(p); - groupMembers.add(Role.AUTHOR); + try { + Principal p; + // Assign minimal role if user has none + if (name == null) { + name = Role.LEARNER; + log.info("===> Found no roles"); + } + p = super.createIdentity(name); + if (!groupMembers.contains(name)) { + log.info("===> Assign user to role " + p.getName()); + group.addMember(p); + groupMembers.add(name); + } + if (name.equals(Role.SYSADMIN) + || name.equals(Role.AUTHOR_ADMIN)) { + p = super.createIdentity(Role.AUTHOR); + log.info("===> Found " + name); + if (!groupMembers.contains(Role.AUTHOR)) { + log.info("===> Assign user to role " + Role.AUTHOR); + group.addMember(p); + groupMembers.add(Role.AUTHOR); + } + } + } catch (Exception e) { + log.debug("===> Failed to create principal: " + name, e); + } + } while (rs.next()); + } catch (NamingException ex) { + throw new LoginException(ex.toString(true)); + } catch (SQLException ex) { + super.log.error("SQL failure", ex); + throw new LoginException(ex.toString()); + } finally { + if (rs != null) { + try { + rs.close(); + } catch (SQLException e) { + } } - } - } catch (Exception e) { - log.debug("===> Failed to create principal: " + name, e); + if (ps != null) { + try { + ps.close(); + } catch (SQLException e) { + } + } + if (conn != null) { + try { + conn.close(); + } catch (Exception ex) { + } + } } - } while (rs.next()); - } catch (NamingException ex) { - throw new LoginException(ex.toString(true)); - } catch (SQLException ex) { - super.log.error("SQL failure", ex); - throw new LoginException(ex.toString()); - } finally { - if (rs != null) { - try { - rs.close(); - } catch (SQLException e) { - } - } - if (ps != null) { - try { - ps.close(); - } catch (SQLException e) { - } - } - if (conn != null) { - try { - conn.close(); - } catch (Exception ex) { - } - } + + Group[] roleSets = new Group[setsMap.size()]; + setsMap.values().toArray(roleSets); + return roleSets; } - Group[] roleSets = new Group[setsMap.size()]; - setsMap.values().toArray(roleSets); - return roleSets; - } + /** + * Overriden to return an empty password string as typically one cannot + * obtain a user's password. We also override the validatePassword so this + * is ok. + * + * @return and empty password String + */ + protected String getUsersPassword() throws LoginException { + return ""; + } - /** - * Overriden to return an empty password string as typically one cannot - * obtain a user's password. We also override the validatePassword so this - * is ok. - * - * @return and empty password String - */ - protected String getUsersPassword() throws LoginException { - return ""; - } - } \ No newline at end of file Index: lams_central/src/java/org/lamsfoundation/lams/web/LoginRequestServlet.java =================================================================== diff -u -rf685d0c3f89b3efd86c9fbdf22742425247f5e3a -rab19db088359a46353cc92e806c40ff5cff818b9 --- lams_central/src/java/org/lamsfoundation/lams/web/LoginRequestServlet.java (.../LoginRequestServlet.java) (revision f685d0c3f89b3efd86c9fbdf22742425247f5e3a) +++ lams_central/src/java/org/lamsfoundation/lams/web/LoginRequestServlet.java (.../LoginRequestServlet.java) (revision ab19db088359a46353cc92e806c40ff5cff818b9) @@ -59,185 +59,184 @@ import org.springframework.web.context.support.WebApplicationContextUtils; /** - * The LoginRequestServlet handles login request by an integrated external - * system. This servlet checks for the UserId, Timestamp, Hash and ServerId if - * it's valid it fetch the password from database and pass it to - * j_security_check for authentication + * The LoginRequestServlet handles login request by an integrated external system. This servlet checks for the UserId, + * Timestamp, Hash and ServerId if it's valid it fetch the password from database and pass it to j_security_check for + * authentication * * @author Fei Yang, Anthony Xiao */ @SuppressWarnings("serial") public class LoginRequestServlet extends HttpServlet { - private static Logger log = Logger.getLogger(LoginRequestServlet.class); + private static Logger log = Logger.getLogger(LoginRequestServlet.class); - private static IntegrationService integrationService = null; + private static IntegrationService integrationService = null; - private static final String JNDI_DATASOURCE = "java:/jdbc/lams-ds"; + private static final String JNDI_DATASOURCE = "java:/jdbc/lams-ds"; - private static final String PASSWORD_QUERY = "select password from lams_user where login=?"; + private static final String PASSWORD_QUERY = "select password from lams_user where login=?"; - /** - * The doGet method of the servlet.
- * - * This method is called when a form has its tag value method equals to get. - * - * @param request - * the request send by the client to the server - * @param response - * the response send by the server to the client - * @throws ServletException - * if an error occurred - * @throws IOException - * if an error occurred - */ - public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - // create http session required by the valve - // since valve can't create session - /*HttpSession hses = request.getSession(); - if(hses!=null) hses.invalidate(); - HttpSession sharedsession = SessionManager.getSession(); - if(sharedsession!=null) sharedsession.invalidate();*/ - HttpSession hses = request.getSession(true); + /** + * The doGet method of the servlet.
+ * + * This method is called when a form has its tag value method equals to get. + * + * @param request + * the request send by the client to the server + * @param response + * the response send by the server to the client + * @throws ServletException + * if an error occurred + * @throws IOException + * if an error occurred + */ + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + // create http session required by the valve + // since valve can't create session + /* + * HttpSession hses = request.getSession(); if(hses!=null) hses.invalidate(); HttpSession sharedsession = + * SessionManager.getSession(); if(sharedsession!=null) sharedsession.invalidate(); + */ + HttpSession hses = request.getSession(true); - String extUsername = request.getParameter(LoginRequestDispatcher.PARAM_USER_ID); - String serverId = request.getParameter(LoginRequestDispatcher.PARAM_SERVER_ID); - String extCourseId = request.getParameter(LoginRequestDispatcher.PARAM_COURSE_ID); - String timestamp = request.getParameter(LoginRequestDispatcher.PARAM_TIMESTAMP); - String hash = request.getParameter(LoginRequestDispatcher.PARAM_HASH); - String method = request.getParameter(LoginRequestDispatcher.PARAM_METHOD); - String countryIsoCode = request.getParameter(LoginRequestDispatcher.PARAM_COUNTRY); - String langIsoCode = request.getParameter(LoginRequestDispatcher.PARAM_LANGUAGE); - String courseName = request.getParameter(CentralConstants.PARAM_COURSE_NAME); + String extUsername = request.getParameter(LoginRequestDispatcher.PARAM_USER_ID); + String serverId = request.getParameter(LoginRequestDispatcher.PARAM_SERVER_ID); + String extCourseId = request.getParameter(LoginRequestDispatcher.PARAM_COURSE_ID); + String timestamp = request.getParameter(LoginRequestDispatcher.PARAM_TIMESTAMP); + String hash = request.getParameter(LoginRequestDispatcher.PARAM_HASH); + String method = request.getParameter(LoginRequestDispatcher.PARAM_METHOD); + String countryIsoCode = request.getParameter(LoginRequestDispatcher.PARAM_COUNTRY); + String langIsoCode = request.getParameter(LoginRequestDispatcher.PARAM_LANGUAGE); + String courseName = request.getParameter(CentralConstants.PARAM_COURSE_NAME); - // implicit login params - String firstName = request.getParameter(LoginRequestDispatcher.PARAM_FIRST_NAME); - String lastName = request.getParameter(LoginRequestDispatcher.PARAM_LAST_NAME); - String email = request.getParameter(LoginRequestDispatcher.PARAM_EMAIL); + // implicit login params + String firstName = request.getParameter(LoginRequestDispatcher.PARAM_FIRST_NAME); + String lastName = request.getParameter(LoginRequestDispatcher.PARAM_LAST_NAME); + String email = request.getParameter(LoginRequestDispatcher.PARAM_EMAIL); - if (extUsername == null || method == null || serverId == null || timestamp == null || hash == null - || extCourseId == null) { - response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Login Failed - login parameters missing"); - return; - } - - // LDEV-2196 preserve character encoding if necessary - if (request.getCharacterEncoding() == null) { - log.debug("request.getCharacterEncoding is empty, parsing username and courseName as 8859_1 to UTF-8..."); - extUsername = new String(extUsername.getBytes("8859_1"), "UTF-8"); - if (courseName != null) { - courseName = new String(courseName.getBytes("8859_1"), "UTF-8"); - } - } - - ExtServerOrgMap serverMap = getService().getExtServerOrgMap(serverId); + if (extUsername == null || method == null || serverId == null || timestamp == null || hash == null + || extCourseId == null) { + response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Login Failed - login parameters missing"); + return; + } - try { - ExtUserUseridMap userMap = null; + // LDEV-2196 preserve character encoding if necessary + if (request.getCharacterEncoding() == null) { + log.debug("request.getCharacterEncoding is empty, parsing username and courseName as 8859_1 to UTF-8..."); + extUsername = new String(extUsername.getBytes("8859_1"), "UTF-8"); + if (courseName != null) { + courseName = new String(courseName.getBytes("8859_1"), "UTF-8"); + } + } - if (firstName == null && lastName == null) { - userMap = getService().getExtUserUseridMap(serverMap, extUsername); - } else { - userMap = getService().getImplicitExtUserUseridMap(serverMap, extUsername, firstName, lastName, - langIsoCode, countryIsoCode, email); - } + ExtServerOrgMap serverMap = getService().getExtServerOrgMap(serverId); - Authenticator.authenticate(serverMap, timestamp, extUsername, method, hash); - ExtCourseClassMap orgMap = getService().getExtCourseClassMap(serverMap, userMap, extCourseId, - countryIsoCode, langIsoCode, courseName, method); - User user = userMap.getUser(); - String login = user.getLogin(); - //was using hses.inNew() API to check if the external user has logged in yet, - // but it seems not working. The "extUser" attribute works as a flag to indicate - // if the user has logged in - String loginRequestUsername = (String) hses.getAttribute("extUser"); - if (loginRequestUsername != null && loginRequestUsername.equals(login)) { - String url = LoginRequestDispatcher.getRequestURL(request); - response.sendRedirect(response.encodeRedirectURL(url)); - return; - } else if (loginRequestUsername != null && !loginRequestUsername.equals(login)) { - hses.invalidate(); - hses = request.getSession(true); - } else if (request.getRemoteUser() != null && loginRequestUsername == null) { - hses.invalidate(); - hses = request.getSession(true); - } - Organisation org = orgMap.getOrganisation(); - IUserManagementService userManagementService = integrationService.getService(); - UserOrganisation uo = userManagementService.getUserOrganisation(user.getUserId(), org.getOrganisationId()); - // make sure external user has minimal set of roles, i.e. learner - Integer[] roleIds = new Integer[] { Role.ROLE_LEARNER }; - //we have to assign all the roles to the external user here, because once the user logged in, the roles - //are cached in JBoss, all the calls of request.isUserInRole() will be based on the cached roles - Map properties = new HashMap(); - properties.put("userOrganisation.userOrganisationId", uo.getUserOrganisationId()); - for (Integer roleId : roleIds) { - properties.put("role.roleId", roleId); - List list = userManagementService.findByProperties(UserOrganisationRole.class, properties); - if (list == null || list.size() == 0) { - UserOrganisationRole uor = new UserOrganisationRole(uo, (Role) userManagementService.findById( - Role.class, roleId)); - userManagementService.save(uor); + try { + ExtUserUseridMap userMap = null; + + if (firstName == null && lastName == null) { + userMap = getService().getExtUserUseridMap(serverMap, extUsername); + } else { + userMap = getService().getImplicitExtUserUseridMap(serverMap, extUsername, firstName, lastName, + langIsoCode, countryIsoCode, email); + } + + Authenticator.authenticate(serverMap, timestamp, extUsername, method, hash); + ExtCourseClassMap orgMap = getService().getExtCourseClassMap(serverMap, userMap, extCourseId, + countryIsoCode, langIsoCode, courseName, method); + User user = userMap.getUser(); + String login = user.getLogin(); + // was using hses.inNew() API to check if the external user has logged in yet, + // but it seems not working. The "extUser" attribute works as a flag to indicate + // if the user has logged in + String loginRequestUsername = (String) hses.getAttribute("extUser"); + if (loginRequestUsername != null && loginRequestUsername.equals(login)) { + String url = LoginRequestDispatcher.getRequestURL(request); + response.sendRedirect(response.encodeRedirectURL(url)); + return; + } else if (loginRequestUsername != null && !loginRequestUsername.equals(login)) { + hses.invalidate(); + hses = request.getSession(true); + } else if (request.getRemoteUser() != null && loginRequestUsername == null) { + hses.invalidate(); + hses = request.getSession(true); + } + Organisation org = orgMap.getOrganisation(); + IUserManagementService userManagementService = integrationService.getService(); + UserOrganisation uo = userManagementService.getUserOrganisation(user.getUserId(), org.getOrganisationId()); + // make sure external user has minimal set of roles, i.e. learner + Integer[] roleIds = new Integer[] { Role.ROLE_LEARNER }; + // we have to assign all the roles to the external user here, because once the user logged in, the roles + // are cached in JBoss, all the calls of request.isUserInRole() will be based on the cached roles + Map properties = new HashMap(); + properties.put("userOrganisation.userOrganisationId", uo.getUserOrganisationId()); + for (Integer roleId : roleIds) { + properties.put("role.roleId", roleId); + List list = userManagementService.findByProperties(UserOrganisationRole.class, properties); + if (list == null || list.size() == 0) { + UserOrganisationRole uor = new UserOrganisationRole(uo, (Role) userManagementService.findById( + Role.class, roleId)); + userManagementService.save(uor); + } + } + log.debug("Session Id - " + hses.getId()); + // connect to DB and get password here + String pass = getUserPassword(userMap.getUser().getLogin()); + // should post the parameters back so it's little more secure, + // but forward doesn't work, use this until a better method is found + hses.setAttribute("extUser", login); + hses.setAttribute(AttributeNames.USER, user.getUserDTO()); + response.sendRedirect("j_security_check?j_username=" + login + "&j_password=" + pass); + } catch (AuthenticationException e) { + log.error("Authentication error: ", e); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Login Failed - authentication error"); + } catch (UserInfoFetchException e) { + log.error("User fetch info error: ", e); + response.sendError(HttpServletResponse.SC_BAD_GATEWAY, + "Login Failed - failed to fetch user info from the third party server"); + } catch (FailedLoginException e) { + log.error("Login error: ", e); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Login Failed - user was not found"); + } catch (NamingException e) { + log.error("Naming error: ", e); + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); + } catch (SQLException e) { + log.error("Database error: ", e); + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); } - } - log.debug("Session Id - " + hses.getId()); - // connect to DB and get password here - String pass = getUserPassword(userMap.getUser().getLogin()); - // should post the parameters back so it's little more secure, - // but forward doesn't work, use this until a better method is found - hses.setAttribute("extUser", login); - hses.setAttribute(AttributeNames.USER, user.getUserDTO()); - response.sendRedirect("j_security_check?j_username=" + login + "&j_password=" + pass); - } catch (AuthenticationException e) { - log.error("Authentication error: ", e); - response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Login Failed - authentication error"); - } catch (UserInfoFetchException e) { - log.error("User fetch info error: ", e); - response.sendError(HttpServletResponse.SC_BAD_GATEWAY, - "Login Failed - failed to fetch user info from the third party server"); - } catch (FailedLoginException e) { - log.error("Login error: ", e); - response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Login Failed - user was not found"); - } catch (NamingException e) { - log.error("Naming error: ", e); - response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); - } catch (SQLException e) { - log.error("Database error: ", e); - response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); } - } - // using JDBC connection to prevent the caching of passwords by hibernate - private String getUserPassword(String username) throws FailedLoginException, NamingException, SQLException { - InitialContext ctx = new InitialContext(); + // using JDBC connection to prevent the caching of passwords by hibernate + private String getUserPassword(String username) throws FailedLoginException, NamingException, SQLException { + InitialContext ctx = new InitialContext(); - DataSource ds = (DataSource) ctx.lookup(JNDI_DATASOURCE); - Connection conn = null; - String password = null; - try { - conn = ds.getConnection(); - PreparedStatement ps = conn.prepareStatement(PASSWORD_QUERY); - ps.setString(1, username); - ResultSet rs = ps.executeQuery(); + DataSource ds = (DataSource) ctx.lookup(JNDI_DATASOURCE); + Connection conn = null; + String password = null; + try { + conn = ds.getConnection(); + PreparedStatement ps = conn.prepareStatement(PASSWORD_QUERY); + ps.setString(1, username); + ResultSet rs = ps.executeQuery(); - // check if there is any result - if (rs.next() == false) - throw new FailedLoginException("invalid username"); + // check if there is any result + if (rs.next() == false) + throw new FailedLoginException("invalid username"); - password = rs.getString(1); - rs.close(); - } finally { - if (conn != null && !conn.isClosed()) - conn.close(); + password = rs.getString(1); + rs.close(); + } finally { + if (conn != null && !conn.isClosed()) + conn.close(); + } + return password; } - return password; - } - private IntegrationService getService() { - if (integrationService == null) { - integrationService = (IntegrationService) WebApplicationContextUtils.getRequiredWebApplicationContext( - getServletContext()).getBean("integrationService"); + private IntegrationService getService() { + if (integrationService == null) { + integrationService = (IntegrationService) WebApplicationContextUtils.getRequiredWebApplicationContext( + getServletContext()).getBean("integrationService"); + } + return integrationService; } - return integrationService; - } } Index: lams_central/src/java/org/lamsfoundation/lams/web/SIFOpenIDServlet.java =================================================================== diff -u --- lams_central/src/java/org/lamsfoundation/lams/web/SIFOpenIDServlet.java (revision 0) +++ lams_central/src/java/org/lamsfoundation/lams/web/SIFOpenIDServlet.java (revision ab19db088359a46353cc92e806c40ff5cff818b9) @@ -0,0 +1,203 @@ +package org.lamsfoundation.lams.web; + +import java.io.IOException; +import java.text.ParseException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.apache.log4j.Logger; +import org.lamsfoundation.lams.openid.OpenIDConfig; +import org.lamsfoundation.lams.usermanagement.User; +import org.lamsfoundation.lams.usermanagement.service.IUserManagementService; +import org.lamsfoundation.lams.util.CSVUtil; +import org.lamsfoundation.lams.util.Configuration; +import org.lamsfoundation.lams.util.ConfigurationKeys; +import org.lamsfoundation.lams.util.WebUtil; +import org.lamsfoundation.lams.web.util.AttributeNames; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; +import org.verisign.joid.OpenIdException; +import org.verisign.joid.consumer.OpenIdFilter; +import org.verisign.joid.util.UrlUtils; + +public class SIFOpenIDServlet extends HttpServlet { + + private static final long serialVersionUID = -381530224124159008L; + + private static Logger log = Logger.getLogger(SIFOpenIDServlet.class); + + public static String PARAM_OPENID_URL = "openid_url"; + + public static String ERROR_NOT_ENABLED = "OpenID is not enabled for LAMS."; + public static String ERROR_BLACKLISTED = "Your provider is not among the trusted providers, please use the portal for logging in."; + public static String ERROR_NO_ID_PASSED = "Authentication failed, no user id was passed."; + public static String ERROR_AUTH = "Authentication failed, there was an error during authentication, please contact the system administrator."; + public static String ERROR_AUTH_LAMS = "Authentication failed, A user in LAMS did not exist for openid URL: "; + + private IUserManagementService userService = null; + + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + + setService(); + + // Get the user's openid url from the request + String userOpenIDURL = WebUtil.readStrParam(request, PARAM_OPENID_URL, true); + + // Check openid enabled + OpenIDConfig openIDEnabled = (OpenIDConfig) userService.findById(OpenIDConfig.class, OpenIDConfig.KEY_ENABLED); + + // Check if the user is not already logged in + HttpSession session = request.getSession(true); + String loggedInAs = OpenIdFilter.getCurrentUser(session); + + if (openIDEnabled != null && Boolean.parseBoolean(openIDEnabled.getConfigValue())) { + if (loggedInAs == null) { + if (userOpenIDURL == null || userOpenIDURL.equals("")) { + + // No user openid url passed and no session, return to portal + log.error("OpenID authentication failed, no value passed for the openid url"); + redirectToPortal(response, ERROR_NO_ID_PASSED); + + } else { + // Attempt to use openid authentication if it is a trusted + // identity provider + if (isTrustedIdentityProvider(userOpenIDURL)) { + log.info("No session found for user with url: " + userOpenIDURL + + ". Sending authentication request to identity provider."); + + String returnURL = UrlUtils.getBaseUrl(request) + "/OpenIDServlet"; + sendAuthenticationRequest(response, userOpenIDURL, returnURL, returnURL); + } else { + log.error("Identity provider not permitted: " + userOpenIDURL); + redirectToPortal(response, ERROR_BLACKLISTED); + } + } + } else { + // Login to LAMS + log.info("Logging user into LAMS using openid token: " + loggedInAs); + loginUser(loggedInAs, request, response); + } + } else { + redirectToPortal(response, ERROR_NOT_ENABLED); + } + + } + + private void sendAuthenticationRequest(HttpServletResponse response, String userOpenIDURL, String returnTo, + String trustRoot) throws IOException { + try { + String openidRedirectURL = OpenIdFilter.joid().getAuthUrl(userOpenIDURL, returnTo, trustRoot); + response.sendRedirect(openidRedirectURL); + } catch (OpenIdException e) { + log.error("Problem getting openid url.", e); + redirectToPortal(response, ERROR_AUTH); + } catch (IOException e) { + log.error("Error sending redirect request.", e); + redirectToPortal(response, ERROR_AUTH); + } + } + + private boolean isTrustedIdentityProvider(String userOpenIDURL) { + + try { + userOpenIDURL = removeHTTPFromString(userOpenIDURL); + userOpenIDURL = removeTrailingSlashFromString(userOpenIDURL); + userOpenIDURL = removeUserNameFromOpenIDURL(userOpenIDURL); + + OpenIDConfig trustedIDPConfig = (OpenIDConfig) userService.findById(OpenIDConfig.class, + OpenIDConfig.KEY_TRUSTED_IDPS); + if (trustedIDPConfig != null) { + String[] trustedIDPs = CSVUtil.parse(trustedIDPConfig.getConfigValue()); + + for (int i = 0; i < trustedIDPs.length; i++) { + String trustedIDP = trustedIDPs[i]; + trustedIDP = removeHTTPFromString(trustedIDP); + trustedIDP = removeTrailingSlashFromString(trustedIDP); + + if (userOpenIDURL.equals(trustedIDP)) { + return true; + } + } + + } + } catch (ParseException e) { + log.error("Error parsing trusted idp csv"); + } + + return false; + } + + private String removeHTTPFromString(String string) { + if (string.startsWith("http://")) { + return string.substring(7); + } + return string; + } + + private String removeTrailingSlashFromString(String string) { + if (string.endsWith("/")) { + return string.substring(0, string.length() - 1); + } + return string; + } + + private String removeUserNameFromOpenIDURL(String string) { + return string.substring(string.indexOf(".") + 1); + } + + private void loginUser(String userOpenIDURL, HttpServletRequest request, HttpServletResponse response) + throws IOException { + if (userService == null) { + userService = getService(); + } + User user = userService.getUserDTOByOpenidURL(userOpenIDURL); + if (user != null) { + HttpSession hses = request.getSession(true); + + // If there is an authenticated user, log them out + String authenticatedUser = (String) hses.getAttribute("openidUser"); + if (authenticatedUser != null) { + hses.invalidate(); + hses = request.getSession(true); + } + + // set the user's session + hses.setAttribute(AttributeNames.USER, user.getUserDTO()); + hses.setAttribute("openidUser", user.getLogin()); + + String lamsURL = "j_security_check?j_username=" + user.getLogin() + "&j_password=" + user.getPassword(); + response.sendRedirect(lamsURL); + } else { + redirectToPortal(response, ERROR_AUTH_LAMS + userOpenIDURL); + } + } + + private void redirectToPortal(HttpServletResponse response, String errorString) throws IOException { + // Get the portal url + OpenIDConfig portalURLObject = (OpenIDConfig) userService.findById(OpenIDConfig.class, + OpenIDConfig.KEY_PORTAL_URL); + String portalURL = (portalURLObject != null) ? portalURLObject.getConfigValue() : Configuration + .get(ConfigurationKeys.SERVER_URL); + response.sendRedirect(portalURL); + } + + private IUserManagementService getService() { + if (userService == null) { + WebApplicationContext ctx = WebApplicationContextUtils + .getRequiredWebApplicationContext(getServletContext()); + userService = (IUserManagementService) ctx.getBean("userManagementService"); + } + return userService; + } + + private void setService() { + if (userService == null) { + userService = (IUserManagementService) WebApplicationContextUtils.getRequiredWebApplicationContext( + getServletContext()).getBean("userManagementService"); + } + } +} Index: lams_central/web/WEB-INF/struts/struts-config.xml =================================================================== diff -u -r7998a31f6d7d4f1eef3e866bafef22caa012eb7a -rab19db088359a46353cc92e806c40ff5cff818b9 --- lams_central/web/WEB-INF/struts/struts-config.xml (.../struts-config.xml) (revision 7998a31f6d7d4f1eef3e866bafef22caa012eb7a) +++ lams_central/web/WEB-INF/struts/struts-config.xml (.../struts-config.xml) (revision ab19db088359a46353cc92e806c40ff5cff818b9) @@ -403,6 +403,11 @@ path="/pedagogical_planner/grouping.jsp" redirect="false" /> + + context.central - - SystemSessionFilter - - org.lamsfoundation.lams.web.session.SystemSessionFilter - - - hibernateFilter - - org.lamsfoundation.lams.util.CustomizedOpenSessionInViewFilter - + SystemSessionFilter + + org.lamsfoundation.lams.web.session.SystemSessionFilter + + + + + hibernateFilter + + org.lamsfoundation.lams.util.CustomizedOpenSessionInViewFilter + sessionFactoryBeanName coreSessionFactory - - - LocaleFilter - - org.lamsfoundation.lams.web.filter.LocaleFilter - - + - - SystemSessionFilter - *.do - - - SystemSessionFilter - *.jsp - - - SystemSessionFilter - *.xml - - - SystemSessionFilter - *.swf - - - SystemSessionFilter - /servlet/* - - - SystemSessionFilter - /fckeditor/* - - - SystemSessionFilter - /eof/* - - - hibernateFilter - *.do - - - hibernateFilter - *.jsp - - - hibernateFilter - *.xml - - - hibernateFilter - *.swf - - - hibernateFilter - /servlet/* - - - hibernateFilter - /services/* - + + LocaleFilter + + org.lamsfoundation.lams.web.filter.LocaleFilter + + + + OpenIdFilter + org.verisign.joid.consumer.OpenIdFilter + + saveInCookie + true + + + + ignorePaths + /login,/server,/echo + + + - hibernateFilter - /fckeditor/* - + SystemSessionFilter + *.do + - + + SystemSessionFilter + *.jsp + + + + SystemSessionFilter + *.xml + + + + SystemSessionFilter + *.swf + + + + SystemSessionFilter + /servlet/* + + + + SystemSessionFilter + /fckeditor/* + + + + SystemSessionFilter + /eof/* + + + + hibernateFilter + *.do + + + + hibernateFilter + *.jsp + + + + hibernateFilter + *.xml + + + + hibernateFilter + *.swf + + + + hibernateFilter + /servlet/* + + + + hibernateFilter + /services/* + + + + hibernateFilter + /fckeditor/* + + + + hibernateFilter + /LoginRequest + + + + hibernateFilter + /ForgotPasswordRequest + + + + LocaleFilter + *.do + + + + LocaleFilter + *.jsp + + + + LocaleFilter + *.xml + + + + LocaleFilter + *.swf + + + + LocaleFilter + /servlet/* + + + + LocaleFilter + /fckeditor/* + + + + OpenIdFilter +/* + + + hibernateFilter - /LoginRequest - - - hibernateFilter - /ForgotPasswordRequest - - - LocaleFilter - *.do - - - LocaleFilter - *.jsp - - - LocaleFilter - *.xml - - - LocaleFilter - *.swf - - - LocaleFilter - /servlet/* - - - LocaleFilter - /fckeditor/* - + /OpenIDServlet + @@ -408,6 +459,13 @@ org.jabber.JabberHTTPBind.JHBServlet + + OpenIDServlet + + org.lamsfoundation.lams.web.SIFOpenIDServlet + + + action *.do @@ -453,6 +511,11 @@ /JHB/* + + OpenIDServlet + /OpenIDServlet + + forgotPasswordServlet /ForgotPasswordRequest Index: lams_common/conf/hibernate/mappings/org/lamsfoundation/lams/openid/OpenIDConfig.hbm.xml =================================================================== diff -u --- lams_common/conf/hibernate/mappings/org/lamsfoundation/lams/openid/OpenIDConfig.hbm.xml (revision 0) +++ lams_common/conf/hibernate/mappings/org/lamsfoundation/lams/openid/OpenIDConfig.hbm.xml (revision ab19db088359a46353cc92e806c40ff5cff818b9) @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file Index: lams_common/conf/hibernate/mappings/org/lamsfoundation/lams/usermanagement/User.hbm.xml =================================================================== diff -u -r9c7d0817bb20458f827a11ac66836dc2530242f9 -rab19db088359a46353cc92e806c40ff5cff818b9 --- lams_common/conf/hibernate/mappings/org/lamsfoundation/lams/usermanagement/User.hbm.xml (.../User.hbm.xml) (revision 9c7d0817bb20458f827a11ac66836dc2530242f9) +++ lams_common/conf/hibernate/mappings/org/lamsfoundation/lams/usermanagement/User.hbm.xml (.../User.hbm.xml) (revision ab19db088359a46353cc92e806c40ff5cff818b9) @@ -569,6 +569,19 @@ + + + @hibernate.property + column="openid_url" + length="255" + + + 0) { log.info(name + " = " + values[0]); - if (name.toLowerCase().equals("location") - && values[0].matches(".*" + Constants.FORM_ACTION + ".*")) { + if (name.toLowerCase().equals("location") && values[0].matches(".*" + Constants.FORM_ACTION + ".*")) { isLoginSuccessful = true; } } else { @@ -93,7 +95,7 @@ } else { HttpSession hses = hreq.getSession(false); - log.debug("Session Id - "+hses.getId()); + log.debug("Session Id - " + hses.getId()); String userid = hreq.getParameter(PARAM_USERID); // get the redirect url from RequestDispatcher @@ -104,9 +106,8 @@ // check required parameters if (userid != null && redirect != null && hses != null) { log.info("LOGIN REQUEST DETECTED - LOGIN SUCCESSFUL"); - log.info("character encoding of the request - " - + request.getCharacterEncoding()); - //redirect = URLDecoder.decode(redirect, "US-ASCII"); + log.info("character encoding of the request - " + request.getCharacterEncoding()); + // redirect = URLDecoder.decode(redirect, "US-ASCII"); log.info("Redirect URL - " + redirect); // create catalina internal session Session session = request.getContext().getManager().findSession(hses.getId()); @@ -126,6 +127,49 @@ log.info("LOGIN REQUEST DETECTED - BUT MISSING REQUIRED PARAM"); } } + } else if (hreq.getRequestURI().endsWith(OPENID_REQUEST)) { + boolean isLoginSuccessful = false; + String[] headerNames = response.getHeaderNames(); + log.info("There are " + headerNames.length + " headers in the response"); + for (String name : headerNames) { + String[] values = response.getHeaderValues(name); + if (values.length > 0) { + log.info(name + " = " + values[0]); + if (name.toLowerCase().equals("location") && values[0].matches(".*" + Constants.FORM_ACTION + ".*")) { + isLoginSuccessful = true; + } + } else { + log.info("empty header-" + name); + } + } + + if (!isLoginSuccessful) { + log.info("OPENID REQUEST DETECTED - BUT NO LOGIN IS CARRIED OUT"); + } else { + + HttpSession hses = hreq.getSession(false); + log.debug("Session Id - " + hses.getId()); + + // check required parameters + if (hses != null) { + log.info("OPENID REQUEST DETECTED - LOGIN SUCCESSFUL"); + + String relURL = request.getContextPath() + "/index.do"; + log.debug("Redirect URL - " + relURL); + + // create catalina internal session + Session session = request.getContext().getManager().findSession(hses.getId()); + + // Create and populate a SavedRequest object for this request + SavedRequest saved = new SavedRequest(); + saved.setRequestURI(relURL); + + // Tomcat's FormAuthenticator looks at Constants.FORM_REQUEST_NOTEfor the redirect object + session.setNote(Constants.FORM_REQUEST_NOTE, saved); + } else { + log.error("LOGIN REQUEST DETECTED - BUT MISSING REQUIRED PARAM"); + } + } } } Index: lams_common/src/java/org/lamsfoundation/lams/openid/OpenIDConfig.java =================================================================== diff -u --- lams_common/src/java/org/lamsfoundation/lams/openid/OpenIDConfig.java (revision 0) +++ lams_common/src/java/org/lamsfoundation/lams/openid/OpenIDConfig.java (revision ab19db088359a46353cc92e806c40ff5cff818b9) @@ -0,0 +1,35 @@ +package org.lamsfoundation.lams.openid; + +public class OpenIDConfig { + private String configKey; + private String configValue; + + public static final String KEY_ENABLED = "enabled"; + public static final String KEY_PORTAL_URL = "portalURL"; + public static final String KEY_TRUSTED_IDPS = "trustedIDPs"; + + public OpenIDConfig () { + } + + /** + * @hibernate.id type="java.lang.Long" column="config_key" + */ + public String getConfigKey() { + return configKey; + } + + public void setConfigKey(String configKey) { + this.configKey = configKey; + } + + /** + * @hibernate.property column="config_value" + */ + public String getConfigValue() { + return configValue; + } + + public void setConfigValue(String configValue) { + this.configValue = configValue; + } +} Index: lams_common/src/java/org/lamsfoundation/lams/usermanagement/User.java =================================================================== diff -u -r9c7d0817bb20458f827a11ac66836dc2530242f9 -rab19db088359a46353cc92e806c40ff5cff818b9 --- lams_common/src/java/org/lamsfoundation/lams/usermanagement/User.java (.../User.java) (revision 9c7d0817bb20458f827a11ac66836dc2530242f9) +++ lams_common/src/java/org/lamsfoundation/lams/usermanagement/User.java (.../User.java) (revision ab19db088359a46353cc92e806c40ff5cff818b9) @@ -176,7 +176,11 @@ private Set recentlyModifiedLearningDesigns = new LinkedHashSet(); private Date modifiedDate; + + /** persistent field */ + private String openidURL; + // ------- TIMEZONES (hardcoded, there is no need to put them into database -------------- public static String[] timezoneList = new String[] { "GMT-12", "GMT-11", "GMT-10", "GMT-9", "GMT-8", "GMT-7", @@ -933,4 +937,18 @@ this.modifiedDate = modifiedDate; } + /** + * @hibernate.property column="openid_url" not-null="false" + * @return + */ + public String getOpenidURL() { + return openidURL; + } + + public void setOpenidURL(String openidURL) { + this.openidURL = openidURL; + } + + + } \ No newline at end of file Index: lams_common/src/java/org/lamsfoundation/lams/usermanagement/service/IUserManagementService.java =================================================================== diff -u -r3a1ae19ae51ede32cec920fc9bb07f6787f562ff -rab19db088359a46353cc92e806c40ff5cff818b9 --- lams_common/src/java/org/lamsfoundation/lams/usermanagement/service/IUserManagementService.java (.../IUserManagementService.java) (revision 3a1ae19ae51ede32cec920fc9bb07f6787f562ff) +++ lams_common/src/java/org/lamsfoundation/lams/usermanagement/service/IUserManagementService.java (.../IUserManagementService.java) (revision ab19db088359a46353cc92e806c40ff5cff818b9) @@ -37,6 +37,7 @@ import org.lamsfoundation.lams.usermanagement.User; import org.lamsfoundation.lams.usermanagement.UserOrganisation; import org.lamsfoundation.lams.usermanagement.dto.OrganisationDTO; +import org.lamsfoundation.lams.usermanagement.dto.UserDTO; import org.lamsfoundation.lams.usermanagement.dto.UserManageBean; /** @@ -516,4 +517,14 @@ * @return number of deleted rows. */ public int removeUserFromOtherGroups(Integer userId, Integer orgId); + + + /** + * Returns the user dto for the given openidURL if one exists + * used for sif openid login + * + * @param openidURL + * @return + */ + public User getUserDTOByOpenidURL(String openidURL); } \ No newline at end of file Index: lams_common/src/java/org/lamsfoundation/lams/usermanagement/service/UserManagementService.java =================================================================== diff -u -rdbf36da33cfec4c6f6c2b52d31df982b49a63d7e -rab19db088359a46353cc92e806c40ff5cff818b9 --- lams_common/src/java/org/lamsfoundation/lams/usermanagement/service/UserManagementService.java (.../UserManagementService.java) (revision dbf36da33cfec4c6f6c2b52d31df982b49a63d7e) +++ lams_common/src/java/org/lamsfoundation/lams/usermanagement/service/UserManagementService.java (.../UserManagementService.java) (revision ab19db088359a46353cc92e806c40ff5cff818b9) @@ -1221,4 +1221,9 @@ deleteAll(uos); return uos.size(); } + + public User getUserDTOByOpenidURL(String openidURL) { + List results = baseDAO.findByProperty(User.class, "openidURL", openidURL); + return results.isEmpty() ? null : (User) results.get(0); + } } \ No newline at end of file