diff --git a/login-api/.classpath b/login-api/.classpath new file mode 100644 index 0000000..a3a2b88 --- /dev/null +++ b/login-api/.classpath @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/login-api/.project b/login-api/.project new file mode 100644 index 0000000..f22c84c --- /dev/null +++ b/login-api/.project @@ -0,0 +1,17 @@ + + + login-api + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/login-api/api/pom.xml b/login-api/api/pom.xml new file mode 100644 index 0000000..9e1840f --- /dev/null +++ b/login-api/api/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + + login-base + org.sakaiproject + 2.6.0RC1-SNAPSHOT + ../../pom.xml + + sakai-login-api + org.sakaiproject + sakai-login-api + + University of California, Davis + http://sakaiproject.org/ + + 2003 + jar + + shared + + + + org.sakaiproject.kernel + sakai-kernel-api + + + org.sakaiproject.kernel + sakai-component-manager + + + javax.servlet + servlet-api + + + + + + diff --git a/login-api/api/src/java/org/sakaiproject/login/api/Login.java b/login-api/api/src/java/org/sakaiproject/login/api/Login.java new file mode 100644 index 0000000..0ff47ea --- /dev/null +++ b/login-api/api/src/java/org/sakaiproject/login/api/Login.java @@ -0,0 +1,18 @@ +package org.sakaiproject.login.api; + +public interface Login { + + /** + * The default login name if none is specified. + */ + public static final String DEFAULT_LOGIN_CONTEXT = "default"; + + public static final String EXCEPTION_INVALID_CREDENTIALS = "invalid-credentials"; + + public static final String EXCEPTION_MISSING_CREDENTIALS = "missing-credentials"; + + public static final String EXCEPTION_INVALID_WITH_PENALTY = "invalid-credentials-with-penalty"; + + public static final String EXCEPTION_INVALID = "invalid"; + +} diff --git a/login-api/api/src/java/org/sakaiproject/login/api/LoginAdvisor.java b/login-api/api/src/java/org/sakaiproject/login/api/LoginAdvisor.java new file mode 100644 index 0000000..2b13d28 --- /dev/null +++ b/login-api/api/src/java/org/sakaiproject/login/api/LoginAdvisor.java @@ -0,0 +1,16 @@ +package org.sakaiproject.login.api; + + +public interface LoginAdvisor { + + public boolean checkCredentials(LoginCredentials credentials); + + public String getLoginAdvice(LoginCredentials credentials); + + public boolean isAdvisorEnabled(); + + public void setFailure(LoginCredentials credentials); + + public void setSuccess(LoginCredentials credentials); + +} diff --git a/login-api/api/src/java/org/sakaiproject/login/api/LoginCredentials.java b/login-api/api/src/java/org/sakaiproject/login/api/LoginCredentials.java new file mode 100644 index 0000000..32e9835 --- /dev/null +++ b/login-api/api/src/java/org/sakaiproject/login/api/LoginCredentials.java @@ -0,0 +1,91 @@ +/********************************************************************************** + * $URL: $ + * $Id: $ + *********************************************************************************** + * + * Copyright (c) 2008 The Sakai Foundation. + * + * Licensed under the Educational Community License, Version 1.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/ecl1.php + * + * 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.login.api; + +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +public class LoginCredentials { + + private String identifier; + private String password; + private String remoteAddr; + private Map parameterMap; + private String sessionId; + private HttpServletRequest request; + + public LoginCredentials(HttpServletRequest request) { + this.identifier = request.getParameter("eid"); + this.password = request.getParameter("pw"); + this.remoteAddr = request.getRemoteAddr(); + this.parameterMap = request.getParameterMap(); + this.request = request; + } + + public LoginCredentials(String identifier, String password, String remoteAddr) { + this.identifier = identifier; + this.password = password; + this.remoteAddr = remoteAddr; + } + + public String getIdentifier() { + return identifier; + } + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + public String getPassword() { + return password; + } + public void setPassword(String password) { + this.password = password; + } + public String getRemoteAddr() { + return remoteAddr; + } + public void setRemoteAddr(String remoteAddr) { + this.remoteAddr = remoteAddr; + } + public Map getParameterMap() { + return parameterMap; + } + public void setParameterMap(Map parameterMap) { + this.parameterMap = parameterMap; + } + + public String getSessionId() { + return sessionId; + } + + public void setSessionId(String sessionId) { + this.sessionId = sessionId; + } + + public HttpServletRequest getRequest() { + return request; + } + + public void setRequest(HttpServletRequest request) { + this.request = request; + } + +} diff --git a/login-api/api/src/java/org/sakaiproject/login/api/LoginRenderContext.java b/login-api/api/src/java/org/sakaiproject/login/api/LoginRenderContext.java new file mode 100644 index 0000000..718ffa8 --- /dev/null +++ b/login-api/api/src/java/org/sakaiproject/login/api/LoginRenderContext.java @@ -0,0 +1,65 @@ +/********************************************************************************** + * $URL: $ + * $Id: $ + *********************************************************************************** + * + * Copyright (c) 2008 The Sakai Foundation. + * + * Licensed under the Educational Community License, Version 1.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/ecl1.php + * + * 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.login.api; + + +/** + * Knock off on {@link org.sakaiproject.portal.api.PortalRenderContext} + * + * @author jrenfro + * + */ +public interface LoginRenderContext { + + /** + * Set a value against a Key, normally a value might be a String, + * Collection or a Map, but depending on the render engine technology other + * objects may be acceptable. + * + * @param string + * @param value + */ + void put(String string, Object value); + + /** + * Convert the render context to a string suitable for dumping to a log file + * or console. + * + * @return + */ + String dump(); + + /** + * Return true if the context needs this part of the portal + * + * @param includeOption + * @return + */ + boolean uses(String includeOption); + + /** + * Get the render engine associated with this context. + * + * @return + */ + LoginRenderEngine getRenderEngine(); + +} diff --git a/login-api/api/src/java/org/sakaiproject/login/api/LoginRenderEngine.java b/login-api/api/src/java/org/sakaiproject/login/api/LoginRenderEngine.java new file mode 100644 index 0000000..f1630be --- /dev/null +++ b/login-api/api/src/java/org/sakaiproject/login/api/LoginRenderEngine.java @@ -0,0 +1,77 @@ +/********************************************************************************** + * $URL: $ + * $Id: $ + *********************************************************************************** + * + * Copyright (c) 2008 The Sakai Foundation. + * + * Licensed under the Educational Community License, Version 1.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/ecl1.php + * + * 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.login.api; + +import java.io.Writer; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.sakaiproject.tool.api.Placement; + +/** + * Knock off of the {@link org.sakaiproject.portal.api.PortalRenderEngine} + * + * @author jrenfro + */ +public interface LoginRenderEngine { + + /** + * Initialise the render engine + * + * @throws Exception + */ + void init() throws Exception; + + /** + * generate a non thread safe render context for the current + * request/thread/operation + * + * @param request + * @return + */ + LoginRenderContext newRenderContext(HttpServletRequest request); + + /** + * Render a PortalRenderContext against a template. The real template may be + * based on a skining name, out output will be send to the Writer + * + * @param template + * @param rcontext + * @param out + * @throws Exception + */ + void render(String template, LoginRenderContext rcontext, Writer out) + throws Exception; + + /** + * prepare for a forward operation in the render engine, this might include + * modifying the request attributes. + * + * @param req + * @param res + * @param p + * @param skin + */ + void setupForward(HttpServletRequest req, HttpServletResponse res, Placement p, String skin); + + +} diff --git a/login-api/api/src/java/org/sakaiproject/login/api/LoginService.java b/login-api/api/src/java/org/sakaiproject/login/api/LoginService.java new file mode 100644 index 0000000..9bdc121 --- /dev/null +++ b/login-api/api/src/java/org/sakaiproject/login/api/LoginService.java @@ -0,0 +1,91 @@ +/********************************************************************************** + * $URL: $ + * $Id: $ + *********************************************************************************** + * + * Copyright (c) 2008 The Sakai Foundation. + * + * Licensed under the Educational Community License, Version 1.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/ecl1.php + * + * 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.login.api; + +import javax.security.auth.login.LoginException; +import javax.servlet.http.HttpServletRequest; + + +public interface LoginService { + + /** + * Authenticate the passed credentials + * + * @param credentials + * @throws LoginException + */ + public void authenticate(LoginCredentials credentials) throws LoginException; + + /** + * Get markup advice to include in the login screen + * + * @param credentials + * @return + */ + public String getLoginAdvice(LoginCredentials credentials); + + /** + * Get a render engine possibly based on the request + * + * @param context - + * the context from whcih to take the render engine. + * @param request + * @return + */ + LoginRenderEngine getRenderEngine(String context, HttpServletRequest request); + + /** + * Indicate whether the service has any markup advice to include in the login screen + * + * @return true if advice exists, false otherwise + */ + public boolean hasLoginAdvice(); + + /** + * Add a render engine to the available render engines. + * + * @param context - + * the context to rengister the render engine in, as there may be + * more than one portal in a sakai instance, you need to register the + * render engine against a context. The context should match the + * context used by the portal to retrieve its render engine. This is + * dependant on the Portal implementation details. + * @param vengine + * the render engine implementation to register with the portal + * service + */ + void addRenderEngine(String context, LoginRenderEngine vengine); + + /** + * Remove a render engine from the avaialble render engines + * + * @param context - + * the context to deregister the render engine from, as there may be + * more than one portal in a sakai instance, you need to deregister + * the render engine from a context. The context should match the + * context used by the portal to retrieve its render engine. This is + * dependant on the Portal implementation details. + * @param vengine + */ + void removeRenderEngine(String context, LoginRenderEngine vengine); + + +} diff --git a/login-api/api/src/java/org/sakaiproject/login/cover/LoginService.java b/login-api/api/src/java/org/sakaiproject/login/cover/LoginService.java new file mode 100644 index 0000000..2c790d9 --- /dev/null +++ b/login-api/api/src/java/org/sakaiproject/login/cover/LoginService.java @@ -0,0 +1,61 @@ +/********************************************************************************** + * $URL: $ + * $Id: $ + *********************************************************************************** + * + * Copyright (c) 2008 The Sakai Foundation. + * + * Licensed under the Educational Community License, Version 1.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/ecl1.php + * + * 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.login.cover; + +import javax.security.auth.login.LoginException; + +import org.sakaiproject.component.cover.ComponentManager; +import org.sakaiproject.login.api.LoginCredentials; + +public class LoginService { + + /** + * Access the component instance: special cover only method. + * + * @return the component instance. + */ + public static org.sakaiproject.login.api.LoginService getInstance() + { + if (ComponentManager.CACHE_COMPONENTS) + { + if (m_instance == null) + m_instance = (org.sakaiproject.login.api.LoginService) ComponentManager + .get(org.sakaiproject.login.api.LoginService.class); + return m_instance; + } + else + { + return (org.sakaiproject.login.api.LoginService) ComponentManager + .get(org.sakaiproject.login.api.LoginService.class); + } + } + + private static org.sakaiproject.login.api.LoginService m_instance = null; + + + public static void authenticate(LoginCredentials credentials) throws LoginException { + org.sakaiproject.login.api.LoginService service = getInstance(); + if (service == null) return; + + service.authenticate(credentials); + } + +} diff --git a/login-api/api/src/java/org/sakaiproject/login/exceptions/LoginCredentialsNotDefinedException.java b/login-api/api/src/java/org/sakaiproject/login/exceptions/LoginCredentialsNotDefinedException.java new file mode 100644 index 0000000..31ab15b --- /dev/null +++ b/login-api/api/src/java/org/sakaiproject/login/exceptions/LoginCredentialsNotDefinedException.java @@ -0,0 +1,47 @@ +/********************************************************************************** + * $URL: $ + * $Id: $ + *********************************************************************************** + * + * Copyright (c) 2008 The Sakai Foundation. + * + * Licensed under the Educational Community License, Version 1.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/ecl1.php + * + * 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.login.exceptions; + +public class LoginCredentialsNotDefinedException extends Exception { + + private static final long serialVersionUID = 1L; + + public LoginCredentialsNotDefinedException() { + // TODO Auto-generated constructor stub + } + + public LoginCredentialsNotDefinedException(String message) { + super(message); + // TODO Auto-generated constructor stub + } + + public LoginCredentialsNotDefinedException(Throwable cause) { + super(cause); + // TODO Auto-generated constructor stub + } + + public LoginCredentialsNotDefinedException(String message, + Throwable cause) { + super(message, cause); + // TODO Auto-generated constructor stub + } + +} diff --git a/login-api/api/src/java/org/sakaiproject/login/exceptions/LoginPenaltyNotConfiguredException.java b/login-api/api/src/java/org/sakaiproject/login/exceptions/LoginPenaltyNotConfiguredException.java new file mode 100644 index 0000000..b5f850d --- /dev/null +++ b/login-api/api/src/java/org/sakaiproject/login/exceptions/LoginPenaltyNotConfiguredException.java @@ -0,0 +1,46 @@ +/********************************************************************************** + * $URL: $ + * $Id: $ + *********************************************************************************** + * + * Copyright (c) 2008 The Sakai Foundation. + * + * Licensed under the Educational Community License, Version 1.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/ecl1.php + * + * 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.login.exceptions; + +public class LoginPenaltyNotConfiguredException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public LoginPenaltyNotConfiguredException() { + // TODO Auto-generated constructor stub + } + + public LoginPenaltyNotConfiguredException(String message) { + super(message); + // TODO Auto-generated constructor stub + } + + public LoginPenaltyNotConfiguredException(Throwable cause) { + super(cause); + // TODO Auto-generated constructor stub + } + + public LoginPenaltyNotConfiguredException(String message, Throwable cause) { + super(message, cause); + // TODO Auto-generated constructor stub + } + +} diff --git a/login-impl/.classpath b/login-impl/.classpath new file mode 100644 index 0000000..08dfd22 --- /dev/null +++ b/login-impl/.classpath @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/login-impl/.project b/login-impl/.project new file mode 100644 index 0000000..39366dc --- /dev/null +++ b/login-impl/.project @@ -0,0 +1,17 @@ + + + login-impl + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/login-impl/impl/pom.xml b/login-impl/impl/pom.xml new file mode 100644 index 0000000..63d30f5 --- /dev/null +++ b/login-impl/impl/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + login-base + org.sakaiproject + 2.6.0RC1-SNAPSHOT + ../../pom.xml + + sakai-login-impl + org.sakaiproject + sakai-login-impl + + University of California, Davis + http://sakaiproject.org/ + + 2003 + jar + + + + + + org.sakaiproject.kernel + sakai-kernel-api + + + org.sakaiproject.kernel + sakai-kernel-util + + + org.sakaiproject.kernel + sakai-component-manager + + + org.sakaiproject + sakai-login-api + ${sakai.version} + provided + + + commons-logging + commons-logging + 1.0.4 + + + javax.servlet + servlet-api + + + velocity + velocity + 1.4 + + + velocity + velocity-dep + 1.4 + + + + + + + diff --git a/login-impl/impl/src/java/org/sakaiproject/login/impl/LoginServiceComponent.java b/login-impl/impl/src/java/org/sakaiproject/login/impl/LoginServiceComponent.java new file mode 100644 index 0000000..915faf3 --- /dev/null +++ b/login-impl/impl/src/java/org/sakaiproject/login/impl/LoginServiceComponent.java @@ -0,0 +1,169 @@ +/********************************************************************************** + * $URL: $ + * $Id: $ + *********************************************************************************** + * + * Copyright (c) 2008 The Sakai Foundation. + * + * Licensed under the Educational Community License, Version 1.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/ecl1.php + * + * 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.login.impl; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.security.auth.login.LoginException; +import javax.servlet.http.HttpServletRequest; + +import org.sakaiproject.component.cover.ComponentManager; +import org.sakaiproject.component.api.ServerConfigurationService; +import org.sakaiproject.event.api.UsageSessionService; +import org.sakaiproject.login.api.Login; +import org.sakaiproject.login.api.LoginAdvisor; +import org.sakaiproject.login.api.LoginCredentials; +import org.sakaiproject.login.api.LoginRenderEngine; +import org.sakaiproject.login.api.LoginService; + +import org.sakaiproject.user.api.Authentication; +import org.sakaiproject.user.api.AuthenticationException; +import org.sakaiproject.user.api.Evidence; +import org.sakaiproject.user.api.AuthenticationManager; +import org.sakaiproject.util.IdPwEvidence; + + +public abstract class LoginServiceComponent implements LoginService { + + protected abstract AuthenticationManager authenticationManager(); + protected abstract ServerConfigurationService serverConfigurationService(); + protected abstract UsageSessionService usageSessionService(); + + private Map renderEngines = new ConcurrentHashMap(); + + private LoginAdvisor loginAdvisor = null; + + public void addRenderEngine(String context, LoginRenderEngine vengine) { + renderEngines.put(context, vengine); + } + + public void authenticate(LoginCredentials credentials) throws LoginException { + LoginAdvisor loginAdvisor = resolveLoginAdvisor(); + + // Only bother checking login credentials and/or imposing a penalty when the protection level is set + boolean isAdvisorEnabled = loginAdvisor != null && loginAdvisor.isAdvisorEnabled(); + + // authenticate + try + { + String eid = credentials.getIdentifier(); + String pw = credentials.getPassword(); + + boolean isEidEmpty = (eid == null) || (eid.length() == 0); + boolean isPwEmpty = (pw == null) || (pw.length() == 0); + + if (isAdvisorEnabled) { + if (!loginAdvisor.checkCredentials(credentials)) { + throw new LoginException(Login.EXCEPTION_INVALID_CREDENTIALS); + } + } + + if (isEidEmpty || isPwEmpty) + { + throw new AuthenticationException("missing-fields"); + } + + // Do NOT trim the password, since many authentication systems allow whitespace. + eid = eid.trim(); + + Evidence e = new IdPwEvidence(eid, pw); + + Authentication a = authenticationManager().authenticate(e); + + // login the user + if (usageSessionService().login(a, credentials.getRequest())) + { + if (isAdvisorEnabled) + loginAdvisor.setSuccess(credentials); + } + else + { + if (isAdvisorEnabled) + loginAdvisor.setFailure(credentials); + throw new LoginException(Login.EXCEPTION_INVALID); + } + } + catch (AuthenticationException ex) + { + if (ex.getMessage().equals("missing-fields")) + throw new LoginException(Login.EXCEPTION_MISSING_CREDENTIALS); + + boolean isPenaltyImposed = false; + + if (isAdvisorEnabled) { + loginAdvisor.setFailure(credentials); + isPenaltyImposed = !loginAdvisor.checkCredentials(credentials); + } + + if (isPenaltyImposed) + throw new LoginException(Login.EXCEPTION_INVALID_WITH_PENALTY); + else + throw new LoginException(Login.EXCEPTION_INVALID); + } + + } + + public String getLoginAdvice(LoginCredentials credentials) { + LoginAdvisor loginAdvisor = resolveLoginAdvisor(); + + // Only bother checking login credentials and/or imposing a penalty when the protection level is set + boolean isAdvisorEnabled = loginAdvisor != null && loginAdvisor.isAdvisorEnabled(); + + if (isAdvisorEnabled) { + return loginAdvisor.getLoginAdvice(credentials); + } + + return ""; + } + + public LoginRenderEngine getRenderEngine(String context, HttpServletRequest request) + { + // at this point we ignore request but we might use ut to return more + // than one render engine + + if (context == null || context.length() == 0) + { + context = Login.DEFAULT_LOGIN_CONTEXT; + } + + return (LoginRenderEngine) renderEngines.get(context); + } + + public boolean hasLoginAdvice() { + LoginAdvisor loginAdvisor = resolveLoginAdvisor(); + + return loginAdvisor != null && loginAdvisor.isAdvisorEnabled(); + } + + public void removeRenderEngine(String context, LoginRenderEngine vengine) { + renderEngines.remove(context); + } + + private LoginAdvisor resolveLoginAdvisor() { + if (loginAdvisor == null) { + loginAdvisor = (LoginAdvisor)ComponentManager.get(LoginAdvisor.class); + } + + return loginAdvisor; + } + +} diff --git a/login-impl/pack/pom.xml b/login-impl/pack/pom.xml new file mode 100644 index 0000000..083e134 --- /dev/null +++ b/login-impl/pack/pom.xml @@ -0,0 +1,51 @@ + + + 4.0.0 + + login-base + org.sakaiproject + 2.6.0RC1-SNAPSHOT + ../../pom.xml + + sakai-login-pack + org.sakaiproject + sakai-login-pack + + University of California, Davis + http://sakaiproject.org/ + + 2003 + sakai-component + + components + + + + org.sakaiproject + sakai-login-impl + ${sakai.version} + + + + + + ${basedir}/src/bundle + + **/*.config + **/*.properties + **/*.vm + + + + + + ${basedir}/src/testBundle + + **/*.* + + false + + + src/java + + diff --git a/login-impl/pack/src/webapp/WEB-INF/components.xml b/login-impl/pack/src/webapp/WEB-INF/components.xml new file mode 100644 index 0000000..c92e526 --- /dev/null +++ b/login-impl/pack/src/webapp/WEB-INF/components.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/login-render-engine-impl/.classpath b/login-render-engine-impl/.classpath new file mode 100644 index 0000000..b042195 --- /dev/null +++ b/login-render-engine-impl/.classpath @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/login-render-engine-impl/.project b/login-render-engine-impl/.project new file mode 100644 index 0000000..f422a86 --- /dev/null +++ b/login-render-engine-impl/.project @@ -0,0 +1,17 @@ + + + login-render-engine-impl + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/login-render-engine-impl/impl/pom.xml b/login-render-engine-impl/impl/pom.xml new file mode 100644 index 0000000..aaca9a4 --- /dev/null +++ b/login-render-engine-impl/impl/pom.xml @@ -0,0 +1,94 @@ + + + 4.0.0 + + login-base + org.sakaiproject + 2.6.0RC1-SNAPSHOT + ../../pom.xml + + sakai-login-render-engine-impl + org.sakaiproject + sakai-login-render-engine-impl + + University of California, Davis + http://sakaiproject.org/ + + 2003 + jar + + + + + + org.sakaiproject.kernel + sakai-kernel-api + + + org.sakaiproject.kernel + sakai-component-manager + + + org.sakaiproject + sakai-login-api + ${sakai.version} + provided + + + org.sakaiproject.kernel + sakai-kernel-util + + + commons-logging + commons-logging + 1.0.4 + + + javax.servlet + servlet-api + + + velocity + velocity + 1.4 + + + velocity + velocity-dep + 1.4 + + + + + + ${basedir}/src/bundle + + **/*.config + **/*.properties + **/*.vm + + + + src/java + + + ${basedir}/src/testBundle + + **/*.* + + + + ${basedir}/src/test/ + + **/* + + + + ${basedir}/../pack/src/webapp/ + + **/*.* + + + + + diff --git a/login-render-engine-impl/impl/src/bundle/defaultskin/options.config b/login-render-engine-impl/impl/src/bundle/defaultskin/options.config new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/login-render-engine-impl/impl/src/bundle/defaultskin/options.config @@ -0,0 +1 @@ + diff --git a/login-render-engine-impl/impl/src/bundle/nuskin/options.config b/login-render-engine-impl/impl/src/bundle/nuskin/options.config new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/login-render-engine-impl/impl/src/bundle/nuskin/options.config @@ -0,0 +1 @@ + diff --git a/login-render-engine-impl/impl/src/bundle/org/sakaiproject/login/impl/velocity/loginvelocity.config b/login-render-engine-impl/impl/src/bundle/org/sakaiproject/login/impl/velocity/loginvelocity.config new file mode 100644 index 0000000..f02fad6 --- /dev/null +++ b/login-render-engine-impl/impl/src/bundle/org/sakaiproject/login/impl/velocity/loginvelocity.config @@ -0,0 +1,39 @@ +# +# +# Tell Velocity which resource loader we are using +# +resource.loader=file, class +# + +# +# File resource loader information +# +file.resource.loader.description=Velocity File Resource Loader +file.resource.loader.class=org.sakaiproject.login.impl.velocity.WebappLoader +file.resource.loader.cache=true +file.resource.loader.modificationCheckInterval=60 + +# +# Class resource loader information +# +class.resource.loader.description=Velocity Classpath Resource Loader +class.resource.loader.class=org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader +class.resource.loader.cache=true +class.resource.loader.modificationCheckInterval=0 + +# +# Other properties +# +input.encoding=UTF-8 +output.encoding=UTF-8 + +runtime.log.logsystem.class=org.apache.velocity.runtime.log.SimpleLog4JLogSystem +# runtime.log.logsystem.log4j.category=portal.vm +runtime.log.logsystem.log4j.category=vm.none + + +# +# Velocity macro properties +# +velocimacro.permissions.allow.inline=true +velocimacro.permissions.allow.inline.override=true diff --git a/login-render-engine-impl/impl/src/java/org/sakaiproject/login/impl/velocity/LoginRenderEngineContextListener.java b/login-render-engine-impl/impl/src/java/org/sakaiproject/login/impl/velocity/LoginRenderEngineContextListener.java new file mode 100644 index 0000000..b5ab1b4 --- /dev/null +++ b/login-render-engine-impl/impl/src/java/org/sakaiproject/login/impl/velocity/LoginRenderEngineContextListener.java @@ -0,0 +1,65 @@ +/********************************************************************************** + * $URL: $ + * $Id: $ + *********************************************************************************** + * + * Copyright (c) 2008 The Sakai Foundation. + * + * Licensed under the Educational Community License, Version 1.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/ecl1.php + * + * 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.login.impl.velocity; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.sakaiproject.component.cover.ServerConfigurationService; +import org.sakaiproject.login.api.Login; +import org.sakaiproject.login.cover.LoginService; +import org.sakaiproject.tool.cover.SessionManager; + +/** + * Knock off on {@link org.sakaiproject.portal.charon.velocity.PortalRenderEngineContextListener} + * + * @author jrenfro + */ +public class LoginRenderEngineContextListener implements ServletContextListener { + + private static final Log log = LogFactory.getLog(LoginRenderEngineContextListener.class); + + private VelocityLoginRenderEngine vengine; + + public void contextDestroyed(ServletContextEvent sce) { + LoginService.getInstance().removeRenderEngine(Login.DEFAULT_LOGIN_CONTEXT, vengine); + } + + public void contextInitialized(ServletContextEvent event) { + try + { + vengine = new VelocityLoginRenderEngine(); + vengine.setContext(event.getServletContext()); + vengine.setServerConfigurationService(ServerConfigurationService.getInstance()); + vengine.setSessionManager(SessionManager.getInstance()); + vengine.init(); + vengine.setLoginService(LoginService.getInstance()); + LoginService.getInstance().addRenderEngine(Login.DEFAULT_LOGIN_CONTEXT, vengine); + } + catch (Exception ex) + { + log.error("Failed to register render engine with the login service, this is probably fatal ", ex); + } + } + +} diff --git a/login-render-engine-impl/impl/src/java/org/sakaiproject/login/impl/velocity/VelocityLoginRenderContext.java b/login-render-engine-impl/impl/src/java/org/sakaiproject/login/impl/velocity/VelocityLoginRenderContext.java new file mode 100644 index 0000000..62f9cfc --- /dev/null +++ b/login-render-engine-impl/impl/src/java/org/sakaiproject/login/impl/velocity/VelocityLoginRenderContext.java @@ -0,0 +1,139 @@ +package org.sakaiproject.login.impl.velocity; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.context.Context; +import org.sakaiproject.login.api.LoginRenderContext; +import org.sakaiproject.login.api.LoginRenderEngine; + +public class VelocityLoginRenderContext implements LoginRenderContext { + + // Logging + + private static final Log log = LogFactory.getLog(VelocityLoginRenderContext.class); + + // Member variables + + private Context vcontext = new VelocityContext(); + + private boolean debug = false; + + private Map options = null; + + private LoginRenderEngine renderEngine = null; + + // Implementation of LoginRenderContext + + public String dump() { + if (debug) + { + Object[] keys = vcontext.getKeys(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < keys.length; i++) + { + Object o = vcontext.get((String) keys[i]); + dumpObject(sb, keys[i], o); + } + return sb.toString(); + } + else + { + return ""; + } + } + + public Context getVelocityContext() + { + return vcontext; + } + + public void put(String key, Object value) { + vcontext.put(key, value); + } + + public boolean uses(String includeOption) { + if (options == null || includeOption == null) + { + return true; + } + return "true".equals(options.get(includeOption)); + } + + + // Accessors + + public boolean isDebug() + { + return debug; + } + + public void setDebug(boolean debug) + { + this.debug = debug; + } + + public Map getOptions() + { + return options; + } + + public void setOptions(Map options) + { + this.options = options; + } + + public LoginRenderEngine getRenderEngine() { + return renderEngine; + } + + public void setRenderEngine(LoginRenderEngine renderEngine) { + this.renderEngine = renderEngine; + } + + + // Helper methods + + private void dumpObject(StringBuilder sb, Object key, Object o) + { + if (o instanceof Map) + { + sb.append("Property ").append(key).append(":").append(o).append("\n"); + dumpMap(sb, key, (Map) o); + } + else if (o instanceof Collection) + { + sb.append("Property ").append(key).append(":").append(o).append("\n"); + dumpCollection(sb, key, (Collection) o); + } + else + { + sb.append("Property ").append(key).append(":").append(o).append("\n"); + } + } + + private void dumpCollection(StringBuilder sb, Object key, Collection collection) + { + int n = 0; + for (Iterator i = collection.iterator(); i.hasNext();) + { + String keyn = key.toString() + "." + String.valueOf(n); + dumpObject(sb, keyn, i.next()); + n++; + } + } + + private void dumpMap(StringBuilder sb, Object key, Map map) + { + for (Iterator i = map.keySet().iterator(); i.hasNext();) + { + Object keyn = i.next(); + dumpObject(sb, key + "." + keyn, map.get(keyn)); + } + } + +} diff --git a/login-render-engine-impl/impl/src/java/org/sakaiproject/login/impl/velocity/VelocityLoginRenderEngine.java b/login-render-engine-impl/impl/src/java/org/sakaiproject/login/impl/velocity/VelocityLoginRenderEngine.java new file mode 100644 index 0000000..168ba3a --- /dev/null +++ b/login-render-engine-impl/impl/src/java/org/sakaiproject/login/impl/velocity/VelocityLoginRenderEngine.java @@ -0,0 +1,212 @@ +package org.sakaiproject.login.impl.velocity; + +import java.io.InputStream; +import java.io.Writer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.velocity.app.VelocityEngine; +import org.apache.velocity.context.Context; +import org.apache.velocity.runtime.RuntimeConstants; +import org.sakaiproject.component.api.ServerConfigurationService; +import org.sakaiproject.login.api.LoginRenderContext; +import org.sakaiproject.login.api.LoginRenderEngine; +import org.sakaiproject.login.api.LoginService; +import org.sakaiproject.tool.api.Placement; +import org.sakaiproject.tool.api.SessionManager; + +public class VelocityLoginRenderEngine implements LoginRenderEngine { + + // Logging + private static final Log log = LogFactory.getLog(VelocityLoginRenderEngine.class); + + // Member variables + private List availableLoginSkins; + + private boolean debug = false; + + private LoginService loginService; + + private ServerConfigurationService serverConfigurationService; + + private ServletContext context; + + private String loginConfig = "loginvelocity.config"; + + private SessionManager sessionManager; + + private boolean styleAble = false; + + private boolean styleAbleContentSummary = false; + + private VelocityEngine vengine; + + // LoginRenderEngine Implementation + + public void init() throws Exception { + /*try + { + styleAble = serverConfigurationService.getBoolean("portal.styleable", false); + styleAbleContentSummary = serverConfigurationService.getBoolean("portal.styleable.contentSummary", false); + } + catch (Exception ex) + { + log + .warn("No Server configuration service available, assuming default settings "); + }*/ + + if ( sessionManager == null ) { + log.warn("No session Manager, assuming test mode "); + } + + vengine = new VelocityEngine(); + + vengine.setApplicationAttribute(ServletContext.class.getName(), context); + + vengine.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS, "org.apache.velocity.runtime.log.SimpleLog4JLogSystem"); + vengine.setProperty("runtime.log.logsystem.log4j.category", "ve.login"); + + Properties p = new Properties(); + InputStream in = this.getClass().getResourceAsStream(loginConfig); + if ( in == null ) { + throw new RuntimeException("Unable to load configuration " + loginConfig); + } else { + log.info("Loaded " + loginConfig); + } + p.load(in); + vengine.init(p); + availableLoginSkins = new ArrayList(); + Map m = new HashMap(); + m.put("name", "defaultskin"); + m.put("display", "Default"); + availableLoginSkins.add(m); + + vengine.getTemplate("/vm/defaultskin/macros.vm"); + } + + public LoginRenderContext newRenderContext(HttpServletRequest request) { + VelocityLoginRenderContext rc = new VelocityLoginRenderContext(); + rc.setRenderEngine(this); + rc.setDebug(debug); + + rc.put("pageSkins", availableLoginSkins); + String loginSkin = "defaultskin"; + + if (request != null) + { + + HttpSession session = request.getSession(); + loginSkin = (String) session.getAttribute("loginskin"); + String newLoginSkin = request.getParameter("loginskin"); + if (newLoginSkin != null && newLoginSkin.length() > 0) + { + session.setAttribute("loginskin", newLoginSkin); + loginSkin = newLoginSkin; + log.debug("Set Skin To " + loginSkin); + } + else + { + if (loginSkin == null || loginSkin.length() == 0) + { + loginSkin = "defaultskin"; + session.setAttribute("loginskin", loginSkin); + + } + } + rc.put("pageCurrentSkin", loginSkin); + log.debug("Current Skin is " + loginSkin); + } + else + { + log.debug("No Request Object Skin is default"); + rc.put("pageCurrentSkin", "defaultskin"); + } + + try + { + Properties p = new Properties(); + p.load(this.getClass().getResourceAsStream("/" + loginSkin + "/options.config")); + rc.setOptions(p); + } + catch (Exception ex) + { + log.info("No options loaded ", ex); + + } + + return rc; + } + + public void render(String template, LoginRenderContext rcontext, Writer out) + throws Exception { + Context vc = ((VelocityLoginRenderContext) rcontext).getVelocityContext(); + String skin = (String) vc.get("pageCurrentSkin"); + if (skin == null || skin.length() == 0) + { + skin = "defaultskin"; + } + if (!"defaultskin".equals(skin)) + { + vengine.getTemplate("/vm/" + skin + "/macros.vm"); + } + vengine.mergeTemplate("/vm/" + skin + "/" + template + ".vm", + ((VelocityLoginRenderContext) rcontext).getVelocityContext(), out); + } + + public void setupForward(HttpServletRequest req, HttpServletResponse res, + Placement p, String skin) { + + log.error("setupForward not implemented!!! Didn't think I would need this..."); + + } + + // Accessors + + public ServletContext getContext() + { + return context; + } + + public void setContext(ServletContext context) + { + this.context = context; + } + + public ServerConfigurationService getServerConfigurationService() { + return serverConfigurationService; + } + + public void setServerConfigurationService( + ServerConfigurationService serverConfigurationService) { + this.serverConfigurationService = serverConfigurationService; + } + + public SessionManager getSessionManager() { + return sessionManager; + } + + public void setSessionManager(SessionManager sessionManager) { + this.sessionManager = sessionManager; + } + + public LoginService getLoginService() { + return loginService; + } + + public void setLoginService(LoginService loginService) { + this.loginService = loginService; + } + + + +} diff --git a/login-render-engine-impl/impl/src/java/org/sakaiproject/login/impl/velocity/WebappLoader.java b/login-render-engine-impl/impl/src/java/org/sakaiproject/login/impl/velocity/WebappLoader.java new file mode 100644 index 0000000..9e6e169 --- /dev/null +++ b/login-render-engine-impl/impl/src/java/org/sakaiproject/login/impl/velocity/WebappLoader.java @@ -0,0 +1,295 @@ +/* + * Copyright 2003-2006 The Apache Software Foundation. + * + * Licensed under the Apache 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.apache.org/licenses/LICENSE-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. + * + * This code was basaed on the simular code in the Velocity Tools project. + */ + +package org.sakaiproject.login.impl.velocity; + +import java.io.File; +import java.io.InputStream; +import java.util.HashMap; + +import javax.servlet.ServletContext; + +import org.apache.commons.collections.ExtendedProperties; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.runtime.resource.Resource; +import org.apache.velocity.runtime.resource.loader.ResourceLoader; + +/** + * Resource loader that uses the ServletContext of a webapp to load Velocity + * templates. (it's much easier to use with servlets than the standard + * FileResourceLoader, in particular the use of war files is transparent). The + * default search path is '/' (relative to the webapp root), but you can change + * this behaviour by specifying one or more paths by mean of as many + * webapp.resource.loader.path properties as needed in the velocity.properties + * file. All paths must be relative to the root of the webapp. To enable caching + * and cache refreshing the webapp.resource.loader.cache and + * webapp.resource.loader.modificationCheckInterval properties need to be set in + * the velocity.properties file ... auto-reloading of global macros requires the + * webapp.resource.loader.cache property to be set to 'false'. + * + * @author Geir Magnusson Jr. + * @author Nathan Bubna + * @author Claude Brisson + * @version $Id: WebappLoader.java 21575 2007-02-15 13:40:21Z + * ian@caret.cam.ac.uk $ + */ + +public class WebappLoader extends ResourceLoader +{ + + /** The root paths for templates (relative to webapp's root). */ + protected String[] paths = null; + + protected HashMap templatePaths = null; + + protected ServletContext servletContext = null; + + /** + * This is abstract in the base class, so we need it.
+ * NOTE: this expects that the ServletContext has already been placed in the + * runtime's application attributes under its full class name (i.e. + * "javax.servlet.ServletContext"). + * + * @param configuration + * the {@link ExtendedProperties} associated with this resource + * loader. + */ + @Override + public void init(ExtendedProperties configuration) + { + rsvc.debug("WebappLoader : initialization starting."); + + /* get configured paths */ + paths = configuration.getStringArray("path"); + if (paths == null || paths.length == 0) + { + paths = new String[1]; + paths[0] = "/"; + } + else + { + /* make sure the paths end with a '/' */ + for (int i = 0; i < paths.length; i++) + { + if (!paths[i].endsWith("/")) + { + paths[i] += '/'; + } + if ( paths[i].startsWith("./") ) { + paths[i] = paths[i].substring(1); + } + rsvc.info("WebappLoader : added template path - '" + paths[i] + "'"); + } + } + + /* get the ServletContext */ + Object obj = rsvc.getApplicationAttribute(ServletContext.class.getName()); + if (obj instanceof ServletContext) + { + servletContext = (ServletContext) obj; + } + else + { + rsvc.error("WebappLoader : unable to retrieve ServletContext"); + } + + /* init the template paths map */ + templatePaths = new HashMap(); + + rsvc.debug("WebappLoader : initialization complete."); + } + + /** + * Get an InputStream so that the Runtime can build a template with it. + * + * @param name + * name of template to get + * @return InputStream containing the template + * @throws ResourceNotFoundException + * if template not found in classpath. + */ + @Override + public synchronized InputStream getResourceStream(String name) + throws ResourceNotFoundException + { + InputStream result = null; + + if (name == null || name.length() == 0) + { + throw new ResourceNotFoundException( + "WebappLoader : No template name provided"); + } + + /* + * since the paths always ends in '/', make sure the name never starts + * with one + */ + while (name.startsWith("/")) + { + name = name.substring(1); + } + + Exception exception = null; + for (int i = 0; i < paths.length; i++) + { + try + { + result = servletContext.getResourceAsStream(paths[i] + name); + + /* save the path and exit the loop if we found the template */ + if (result != null) + { + templatePaths.put(name, paths[i]); + break; + } + } + catch (Exception e) + { + /* only save the first one for later throwing */ + if (exception == null) + { + exception = e; + } + } + } + + /* if we never found the template */ + if (result == null) + { + String msg; + if (exception == null) + { + msg = "WebappLoader : Resource '" + name + "' not found."; + } + else + { + msg = exception.getMessage(); + } + /* convert to a general Velocity ResourceNotFoundException */ + throw new ResourceNotFoundException(msg); + } + + return result; + } + + private File getCachedFile(String rootPath, String fileName) + { + // we do this when we cache a resource, + // so do it again to ensure a match + while (fileName.startsWith("/")) + { + fileName = fileName.substring(1); + } + + String savedPath = (String) templatePaths.get(fileName); + return new File(rootPath + savedPath, fileName); + } + + /** + * Checks to see if a resource has been deleted, moved or modified. + * + * @param resource + * Resource The resource to check for modification + * @return boolean True if the resource has been modified + */ + @Override + public boolean isSourceModified(Resource resource) + { + String rootPath = servletContext.getRealPath("/"); + if (rootPath == null) + { + // rootPath is null if the servlet container cannot translate the + // virtual path to a real path for any reason (such as when the + // content is being made available from a .war archive) + return false; + } + + // first, try getting the previously found file + String fileName = resource.getName(); + File cachedFile = getCachedFile(rootPath, fileName); + if (!cachedFile.exists()) + { + /* then the source has been moved and/or deleted */ + return true; + } + + /* + * check to see if the file can now be found elsewhere before it is + * found in the previously saved path + */ + File currentFile = null; + for (int i = 0; i < paths.length; i++) + { + currentFile = new File(rootPath + paths[i], fileName); + if (currentFile.canRead()) + { + /* + * stop at the first resource found (just like in + * getResourceStream()) + */ + break; + } + } + + /* if the current is the cached and it is readable */ + if (cachedFile.equals(currentFile) && cachedFile.canRead()) + { + /* then (and only then) do we compare the last modified values */ + return (cachedFile.lastModified() != resource.getLastModified()); + } + else + { + /* + * we found a new file for the resource or the resource is no longer + * readable. + */ + return true; + } + } + + /** + * Checks to see when a resource was last modified + * + * @param resource + * Resource the resource to check + * @return long The time when the resource was last modified or 0 if the + * file can't be read + */ + @Override + public long getLastModified(Resource resource) + { + String rootPath = servletContext.getRealPath("/"); + if (rootPath == null) + { + // rootPath is null if the servlet container cannot translate the + // virtual path to a real path for any reason (such as when the + // content is being made available from a .war archive) + return 0; + } + + File cachedFile = getCachedFile(rootPath, resource.getName()); + if (cachedFile.canRead()) + { + return cachedFile.lastModified(); + } + else + { + return 0; + } + } +} diff --git a/login-render-engine-impl/pack/pom.xml b/login-render-engine-impl/pack/pom.xml new file mode 100644 index 0000000..73bbf87 --- /dev/null +++ b/login-render-engine-impl/pack/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + login-base + org.sakaiproject + 2.6.0RC1-SNAPSHOT + ../../pom.xml + + login-render + org.sakaiproject + login-render + + University of California, Davis + http://sakaiproject.org/ + + 2003 + war + + + org.sakaiproject + sakai-login-render-engine-impl + ${sakai.version} + + + commons-logging + commons-logging + 1.0.4 + + + + + src/java + + diff --git a/login-render-engine-impl/pack/src/webapp/WEB-INF/web.xml b/login-render-engine-impl/pack/src/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..c3172b4 --- /dev/null +++ b/login-render-engine-impl/pack/src/webapp/WEB-INF/web.xml @@ -0,0 +1,12 @@ + + + login + login + + + org.sakaiproject.login.impl.velocity.LoginRenderEngineContextListener + + + \ No newline at end of file diff --git a/login-render-engine-impl/pack/src/webapp/vm/defaultskin/macros.vm b/login-render-engine-impl/pack/src/webapp/vm/defaultskin/macros.vm new file mode 100644 index 0000000..88d4812 --- /dev/null +++ b/login-render-engine-impl/pack/src/webapp/vm/defaultskin/macros.vm @@ -0,0 +1 @@ +## MACROS file is no longer used at all diff --git a/login-render-engine-impl/pack/src/webapp/vm/defaultskin/xlogin.vm b/login-render-engine-impl/pack/src/webapp/vm/defaultskin/xlogin.vm new file mode 100644 index 0000000..ad7ab67 --- /dev/null +++ b/login-render-engine-impl/pack/src/webapp/vm/defaultskin/xlogin.vm @@ -0,0 +1,53 @@ + + + + + + + + ${uiService} + + + + + + + + + + + + + + + + + + diff --git a/login-render-engine-impl/pack/src/webapp/vm/nuskin/macros.vm b/login-render-engine-impl/pack/src/webapp/vm/nuskin/macros.vm new file mode 100644 index 0000000..88d4812 --- /dev/null +++ b/login-render-engine-impl/pack/src/webapp/vm/nuskin/macros.vm @@ -0,0 +1 @@ +## MACROS file is no longer used at all diff --git a/login-render-engine-impl/pack/src/webapp/vm/nuskin/xlogin.vm b/login-render-engine-impl/pack/src/webapp/vm/nuskin/xlogin.vm new file mode 100644 index 0000000..eb8a488 --- /dev/null +++ b/login-render-engine-impl/pack/src/webapp/vm/nuskin/xlogin.vm @@ -0,0 +1,55 @@ + + + + + + + + ${uiService} + + + + + + +
+ Blah! + + + + + + + + + + + + diff --git a/login-tool/tool/pom.xml b/login-tool/tool/pom.xml index 9bfa2fc..a2218b7 100644 --- a/login-tool/tool/pom.xml +++ b/login-tool/tool/pom.xml @@ -52,6 +52,12 @@ commons-logging 1.0.4 + + org.sakaiproject + sakai-login-api + ${sakai.version} + provided + diff --git a/login-tool/tool/src/bundle/auth.properties b/login-tool/tool/src/bundle/auth.properties index 2d243fe..0f4881c 100644 --- a/login-tool/tool/src/bundle/auth.properties +++ b/login-tool/tool/src/bundle/auth.properties @@ -4,7 +4,7 @@ email = email err.logerr = Login Error -gen.alert = Alert: +gen.alert = invalid = Invalid @@ -20,7 +20,13 @@ userid = user id wel.wel = Welcome -log.invalid = invalid login -log.canceled = login canceled +log.invalid = Invalid login +log.canceled = Login canceled + +log.invalid.with.penalty = Login failed. To help us maintain security please type the distorted words \ + or letters shown into the text box below. +log.invalid.credentials = One or more of your credentials were not correctly entered. Please ensure \ + that your user id and password are correct, and type the distorted words or letters shown into \ + the text box below. log.tryagain = Unable to process that login, please try again. diff --git a/login-tool/tool/src/java/org/sakaiproject/login/tool/ContainerLogin.java b/login-tool/tool/src/java/org/sakaiproject/login/tool/ContainerLogin.java index b03aa0b..e824d27 100644 --- a/login-tool/tool/src/java/org/sakaiproject/login/tool/ContainerLogin.java +++ b/login-tool/tool/src/java/org/sakaiproject/login/tool/ContainerLogin.java @@ -128,7 +128,7 @@ public class ContainerLogin extends HttpServlet } // mark the session and redirect (for login failuer or authentication exception) - session.setAttribute(LoginTool.ATTR_CONTAINER_CHECKED, LoginTool.ATTR_CONTAINER_CHECKED); - res.sendRedirect(res.encodeRedirectURL((String) session.getAttribute(LoginTool.ATTR_RETURN_URL))); + session.setAttribute(SkinnableLogin.ATTR_CONTAINER_CHECKED, SkinnableLogin.ATTR_CONTAINER_CHECKED); + res.sendRedirect(res.encodeRedirectURL((String) session.getAttribute(SkinnableLogin.ATTR_RETURN_URL))); } } diff --git a/login-tool/tool/src/java/org/sakaiproject/login/tool/LoginTool.java b/login-tool/tool/src/java/org/sakaiproject/login/tool/LoginTool.java deleted file mode 100644 index 24e72ed..0000000 --- a/login-tool/tool/src/java/org/sakaiproject/login/tool/LoginTool.java +++ /dev/null @@ -1,432 +0,0 @@ -/********************************************************************************** - * $URL$ - * $Id$ - *********************************************************************************** - * - * Copyright (c) 2005, 2006, 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.osedu.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.login.tool; - -import java.io.IOException; -import java.io.PrintWriter; - -import javax.servlet.ServletConfig; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.sakaiproject.component.cover.ServerConfigurationService; -import org.sakaiproject.event.cover.UsageSessionService; -import org.sakaiproject.tool.api.Session; -import org.sakaiproject.tool.api.Tool; -import org.sakaiproject.tool.cover.SessionManager; -import org.sakaiproject.user.api.Authentication; -import org.sakaiproject.user.api.AuthenticationException; -import org.sakaiproject.user.api.Evidence; -import org.sakaiproject.user.cover.AuthenticationManager; -import org.sakaiproject.util.IdPwEvidence; -import org.sakaiproject.util.ResourceLoader; -import org.sakaiproject.util.Validator; -import org.sakaiproject.util.Web; - -/** - *

- * Login tool for Sakai. Works with the ContainerLoginTool servlet to offer container or internal login. - *

- *

- * This "tool", being login, is not placed, instead each user can interact with only one login at a time. The Sakai Session is used for attributes. - *

- */ -public class LoginTool extends HttpServlet -{ - /** Our log (commons). */ - private static Log M_log = LogFactory.getLog(LoginTool.class); - - /** Session attribute used to store a message between steps. */ - protected static final String ATTR_MSG = "sakai.login.message"; - - /** Session attribute set and shared with ContainerLoginTool: URL for redirecting back here. */ - public static final String ATTR_RETURN_URL = "sakai.login.return.url"; - - /** Session attribute set and shared with ContainerLoginTool: if set we have failed container and need to check internal. */ - public static final String ATTR_CONTAINER_CHECKED = "sakai.login.container.checked"; - - /** Marker to indicate we are logging in the PDA Portal and should put out abbreviated HTML */ - public static final String PDA_PORTAL_SUFFIX = "/pda/"; - - private static ResourceLoader rb = new ResourceLoader("auth"); - - /** - * Access the Servlet's information display. - * - * @return servlet information. - */ - public String getServletInfo() - { - return "Sakai Login"; - } - - /** - * Initialize the servlet. - * - * @param config - * The servlet config. - * @throws ServletException - */ - public void init(ServletConfig config) throws ServletException - { - super.init(config); - - M_log.info("init()"); - } - - /** - * Shutdown the servlet. - */ - public void destroy() - { - M_log.info("destroy()"); - - super.destroy(); - } - - /** - * Respond to requests. - * - * @param req - * The servlet request. - * @param res - * The servlet response. - * @throws ServletException. - * @throws IOException. - */ - protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException - { - // get the session - Session session = SessionManager.getCurrentSession(); - - // get my tool registration - Tool tool = (Tool) req.getAttribute(Tool.TOOL); - - // recognize what to do from the path - String option = req.getPathInfo(); - - // maybe we don't want to do the container this time - boolean skipContainer = false; - - // if missing, set it to "/login" - if ((option == null) || ("/".equals(option))) - { - option = "/login"; - } - - // look for the extreme login (i.e. to skip container checks) - else if ("/xlogin".equals(option)) - { - option = "/login"; - skipContainer = true; - } - - // get the parts (the first will be "", second will be "login" or "logout") - String[] parts = option.split("/"); - - if (parts[1].equals("logout")) - { - // get the session info complete needs, since the logout will invalidate and clear the session - String returnUrl = (String) session.getAttribute(Tool.HELPER_DONE_URL); - - // logout the user - UsageSessionService.logout(); - - complete(returnUrl, null, tool, res); - return; - } - else - { - // see if we need to check container - boolean checkContainer = ServerConfigurationService.getBoolean("container.login", false); - if (checkContainer && !skipContainer) - { - // if we have not checked the container yet, check it now - if (session.getAttribute(ATTR_CONTAINER_CHECKED) == null) - { - // save our return path - session.setAttribute(ATTR_RETURN_URL, Web.returnUrl(req, null)); - - String containerCheckPath = this.getServletConfig().getInitParameter("container"); - String containerCheckUrl = Web.serverUrl(req) + containerCheckPath; - - // support query parms in url for container auth - String queryString = Validator.generateQueryString(req);; - if (queryString != null) containerCheckUrl = containerCheckUrl + "?" + queryString; - - res.sendRedirect(res.encodeRedirectURL(containerCheckUrl)); - return; - } - } - - // send the form - sendForm(req, res); - } - } - - /** - * Send the login form - * - * @param req - * Servlet request. - * @param res - * Servlet response. - * @throws IOException - */ - protected void sendForm(HttpServletRequest req, HttpServletResponse res) throws IOException - { - final String headHtml = "" - + "" - + " " - + " " - + " " - + " " - + " " - + " UI.SERVICE" - + " " - + " " - + " " - + " " - + ""; - - final String tailHtml = ""; - - final String loginHtml = "" + " " - + " " + " " + " " + " " + " " + " " + "
" + " Login Required" + "
" - + " " - + "
" - + " MSG" + " " - + " " + " " - + " " - + " " + " " + " " - + " " - + " " + " " + " " - + " " + "
" + " " + " " + " " + "
" + " " + " " + " " + "
" - + " " + "
" + "
" + "
"; - - - // get the Sakai session - Session session = SessionManager.getCurrentSession(); - - // get my tool registration - Tool tool = (Tool) req.getAttribute(Tool.TOOL); - - // fragment or not? - boolean fragment = Boolean.TRUE.toString().equals(req.getAttribute(Tool.FRAGMENT)); - - // PDA or not? - String portalUrl = (String) session.getAttribute(Tool.HELPER_DONE_URL); - boolean isPDA = false; - if ( portalUrl != null ) isPDA = portalUrl.endsWith(PDA_PORTAL_SUFFIX); - - String eidWording = rb.getString("userid"); - String pwWording = rb.getString("log.pass"); - String loginRequired = rb.getString("log.logreq"); - String loginWording = rb.getString("log.login"); - - if (!fragment) - { - // set our response type - res.setContentType("text/html; charset=UTF-8"); - res.addDateHeader("Expires", System.currentTimeMillis() - (1000L * 60L * 60L * 24L * 365L)); - res.addDateHeader("Last-Modified", System.currentTimeMillis()); - res.addHeader("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0"); - res.addHeader("Pragma", "no-cache"); - } - - String defaultSkin = ServerConfigurationService.getString("skin.default"); - String skinRoot = ServerConfigurationService.getString("skin.repo"); - String uiService = ServerConfigurationService.getString("ui.service"); - - // get our response writer - PrintWriter out = res.getWriter(); - - if (!fragment) - { - // start our complete document - String head = headHtml.replaceAll("DEFAULT_SKIN", defaultSkin); - head = head.replaceAll("SKIN_ROOT", skinRoot); - head = head.replaceAll("UI.SERVICE", uiService); - out.println(head); - } - - // if we are in helper mode, there might be a helper message - if (session.getAttribute(Tool.HELPER_MESSAGE) != null) - { - out.println("

" + session.getAttribute(Tool.HELPER_MESSAGE) + "

"); - } - - // add our return URL - String returnUrl = res.encodeURL(Web.returnUrl(req, null)); - String html = loginHtml.replaceAll("ACTION", res.encodeURL(returnUrl)); - - // add our wording - html = html.replaceAll("EID", eidWording); - html = html.replaceAll("PW", pwWording); - html = html.replaceAll("Login Required", loginRequired); - html = html.replaceAll("LoginSubmit", loginWording); - - // add the default skin - html = html.replaceAll("DEFAULT_SKIN", defaultSkin); - html = html.replaceAll("SKIN_ROOT", skinRoot); - if ( isPDA ) - { - html = html.replaceAll("class=\"login\"", "align=\"center\""); - } - - // write a message if present - String msg = (String) session.getAttribute(ATTR_MSG); - if (msg != null) - { - html = html.replaceAll("MSG", "
" + rb.getString("gen.alert") + " " + msg + "
"); - session.removeAttribute(ATTR_MSG); - } - else - { - html = html.replaceAll("MSG", ""); - } - - // write the login screen - out.println(html); - - if (!fragment) - { - // close the complete document - out.println(tailHtml); - } - } - - /** - * Respond to data posting requests. - * - * @param req - * The servlet request. - * @param res - * The servlet response. - * @throws ServletException. - * @throws IOException. - */ - protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException - { - // get the Sakai session - Session session = SessionManager.getCurrentSession(); - - // get my tool registration - Tool tool = (Tool) req.getAttribute(Tool.TOOL); - - // here comes the data back from the form... these fields will be present, blank if not filled in - String eid = req.getParameter("eid"); - String pw = req.getParameter("pw"); - - // one of these will be there, one null, depending on how the submit was done - String submit = req.getParameter("submit"); - String cancel = req.getParameter("cancel"); - - // cancel - if (cancel != null) - { - session.setAttribute(ATTR_MSG, rb.getString("log.canceled")); - - // get the session info complete needs, since the logout will invalidate and clear the session - String returnUrl = (String) session.getAttribute(Tool.HELPER_DONE_URL); - - // TODO: send to the cancel URL, cleanup session - complete(returnUrl, session, tool, res); - } - - // submit - else - { - // authenticate - try - { - if ((eid == null) || (pw == null) || (eid.length() == 0) || (pw.length() == 0)) - { - throw new AuthenticationException("missing required fields"); - } - - // Do NOT trim the password, since many authentication systems allow whitespace. - eid = eid.trim(); - - Evidence e = new IdPwEvidence(eid, pw); - - Authentication a = AuthenticationManager.authenticate(e); - - // login the user - if (UsageSessionService.login(a, req)) - { - // get the session info complete needs, since the logout will invalidate and clear the session - String returnUrl = (String) session.getAttribute(Tool.HELPER_DONE_URL); - - complete(returnUrl, session, tool, res); - } - else - { - session.setAttribute(ATTR_MSG, rb.getString("log.tryagain")); - res.sendRedirect(res.encodeRedirectURL(Web.returnUrl(req, null))); - } - } - catch (AuthenticationException ex) - { - session.setAttribute(ATTR_MSG, rb.getString("log.invalid")); - - // respond with a redirect back here - res.sendRedirect(res.encodeRedirectURL(Web.returnUrl(req, null))); - } - } - } - - /** - * Cleanup and redirect when we have a successful login / logout - * - * @param session - * @param tool - * @param res - * @throws IOException - */ - protected void complete(String returnUrl, Session session, Tool tool, HttpServletResponse res) throws IOException - { - // cleanup session - if (session != null) - { - session.removeAttribute(Tool.HELPER_MESSAGE); - session.removeAttribute(Tool.HELPER_DONE_URL); - session.removeAttribute(ATTR_MSG); - session.removeAttribute(ATTR_RETURN_URL); - session.removeAttribute(ATTR_CONTAINER_CHECKED); - } - - // if we end up with nowhere to go, go to the portal - if (returnUrl == null) - { - returnUrl = ServerConfigurationService.getPortalUrl(); - M_log.info("complete: nowhere set to go, going to portal"); - } - - // redirect to the done URL - res.sendRedirect(res.encodeRedirectURL(returnUrl)); - } -} diff --git a/login-tool/tool/src/java/org/sakaiproject/login/tool/SkinnableLogin.java b/login-tool/tool/src/java/org/sakaiproject/login/tool/SkinnableLogin.java new file mode 100644 index 0000000..3bb2015 --- /dev/null +++ b/login-tool/tool/src/java/org/sakaiproject/login/tool/SkinnableLogin.java @@ -0,0 +1,370 @@ +/********************************************************************************** + * $URL: $ + * $Id: $ + *********************************************************************************** + * + * Copyright (c) 2008 The Sakai Foundation. + * + * Licensed under the Educational Community License, Version 1.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/ecl1.php + * + * 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.login.tool; + +import java.io.IOException; +import java.io.PrintWriter; + +import javax.security.auth.login.LoginException; +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.sakaiproject.component.cover.ServerConfigurationService; +import org.sakaiproject.event.cover.UsageSessionService; +import org.sakaiproject.login.api.Login; +import org.sakaiproject.login.api.LoginCredentials; +import org.sakaiproject.login.api.LoginRenderContext; +import org.sakaiproject.login.api.LoginRenderEngine; +import org.sakaiproject.login.api.LoginService; +import org.sakaiproject.tool.api.Session; +import org.sakaiproject.tool.api.Tool; +import org.sakaiproject.tool.cover.SessionManager; +import org.sakaiproject.util.ResourceLoader; +import org.sakaiproject.util.Web; + + +public class SkinnableLogin extends HttpServlet implements Login { + + private static final long serialVersionUID = 1L; + + /** Our log (commons). */ + private static Log log = LogFactory.getLog(SkinnableLogin.class); + + /** Session attribute used to store a message between steps. */ + protected static final String ATTR_MSG = "notify"; + + /** Session attribute set and shared with ContainerLoginTool: URL for redirecting back here. */ + public static final String ATTR_RETURN_URL = "sakai.login.return.url"; + + /** Session attribute set and shared with ContainerLoginTool: if set we have failed container and need to check internal. */ + public static final String ATTR_CONTAINER_CHECKED = "sakai.login.container.checked"; + + /** Marker to indicate we are logging in the PDA Portal and should put out abbreviated HTML */ + public static final String PDA_PORTAL_SUFFIX = "/pda/"; + + private static ResourceLoader rb = new ResourceLoader("auth"); + + private LoginService loginService; + + private String loginContext; + + + public void init(ServletConfig config) throws ServletException + { + super.init(config); + loginContext = config.getInitParameter("login.context"); + if (loginContext == null || loginContext.length() == 0) + { + loginContext = DEFAULT_LOGIN_CONTEXT; + } + + loginService = org.sakaiproject.login.cover.LoginService.getInstance(); + + log.info("init()"); + } + + public void destroy() + { + log.info("destroy()"); + + super.destroy(); + } + + /** + * Access the Servlet's information display. + * + * @return servlet information. + */ + public String getServletInfo() + { + return "Sakai Login"; + } + + protected void doGet(HttpServletRequest req, HttpServletResponse res) + throws ServletException, IOException + { + // get the session + Session session = SessionManager.getCurrentSession(); + + // get my tool registration + Tool tool = (Tool) req.getAttribute(Tool.TOOL); + + // recognize what to do from the path + String option = req.getPathInfo(); + + // maybe we don't want to do the container this time + boolean skipContainer = false; + + // if missing, set it to "/login" + if ((option == null) || ("/".equals(option))) + { + option = "/login"; + } + + // look for the extreme login (i.e. to skip container checks) + else if ("/xlogin".equals(option)) + { + option = "/login"; + skipContainer = true; + } + + // get the parts (the first will be "", second will be "login" or "logout") + String[] parts = option.split("/"); + + if (parts[1].equals("logout")) + { + // get the session info complete needs, since the logout will invalidate and clear the session + String returnUrl = (String) session.getAttribute(Tool.HELPER_DONE_URL); + + // logout the user + UsageSessionService.logout(); + + complete(returnUrl, null, tool, res); + return; + } + + // see if we need to check container + boolean checkContainer = ServerConfigurationService.getBoolean("container.login", false); + if (checkContainer && !skipContainer) + { + // if we have not checked the container yet, check it now + if (session.getAttribute(ATTR_CONTAINER_CHECKED) == null) + { + // save our return path + session.setAttribute(ATTR_RETURN_URL, Web.returnUrl(req, null)); + + String containerCheckPath = this.getServletConfig().getInitParameter("container"); + String containerCheckUrl = Web.serverUrl(req) + containerCheckPath; + + // support query parms in url for container auth + String queryString = req.getQueryString(); + if (queryString != null) containerCheckUrl = containerCheckUrl + "?" + queryString; + + res.sendRedirect(res.encodeRedirectURL(containerCheckUrl)); + return; + } + } + + // Present the xlogin template + LoginRenderContext rcontext = startPageContext("", req, res); + sendResponse(rcontext, res, "xlogin", null); + } + + /** + * Respond to data posting requests. + * + * @param req + * The servlet request. + * @param res + * The servlet response. + * @throws ServletException. + * @throws IOException. + */ + protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException + { + // Present the xlogin template + LoginRenderContext rcontext = startPageContext(null, req, res); + + // Get the Sakai session + Session session = SessionManager.getCurrentSession(); + + // Get my tool registration + Tool tool = (Tool) req.getAttribute(Tool.TOOL); + + // Determine if the user canceled this request + String cancel = req.getParameter("cancel"); + + // cancel + if (cancel != null) + { + rcontext.put(ATTR_MSG, rb.getString("log.canceled")); + + // get the session info complete needs, since the logout will invalidate and clear the session + String returnUrl = (String) session.getAttribute(Tool.HELPER_DONE_URL); + + // TODO: send to the cancel URL, cleanup session + complete(returnUrl, session, tool, res); + } + + // submit + else + { + LoginCredentials credentials = new LoginCredentials(req); + credentials.setSessionId(session.getId()); + + try { + loginService.authenticate(credentials); + String returnUrl = (String) session.getAttribute(Tool.HELPER_DONE_URL); + complete(returnUrl, session, tool, res); + + } catch (LoginException le) { + + String message = le.getMessage(); + + log.debug("LoginException: " + message); + + boolean showAdvice = false; + + if (message.equals(EXCEPTION_INVALID_CREDENTIALS)) { + rcontext.put(ATTR_MSG, rb.getString("log.invalid.credentials")); + showAdvice = true; + } else if (message.equals(EXCEPTION_INVALID_WITH_PENALTY)) { + rcontext.put(ATTR_MSG, rb.getString("log.invalid.with.penalty")); + showAdvice = true; + } else if (message.equals(EXCEPTION_MISSING_CREDENTIALS)) + rcontext.put(ATTR_MSG, rb.getString("log.tryagain")); + else + rcontext.put(ATTR_MSG, rb.getString("log.invalid")); + + if (showAdvice) { + String loginAdvice = loginService.getLoginAdvice(credentials); + if (loginAdvice != null && !loginAdvice.equals("")) { + log.debug("Returning login advice"); + rcontext.put("loginAdvice", loginAdvice); + } + } + + sendResponse(rcontext, res, "xlogin", null); + } + } + } + + public void sendResponse(LoginRenderContext rcontext, HttpServletResponse res, + String template, String contentType) throws IOException + { + // headers + if (contentType == null) + { + res.setContentType("text/html; charset=UTF-8"); + } + else + { + res.setContentType(contentType); + } + res.addDateHeader("Expires", System.currentTimeMillis() + - (1000L * 60L * 60L * 24L * 365L)); + res.addDateHeader("Last-Modified", System.currentTimeMillis()); + res.addHeader("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0"); + res.addHeader("Pragma", "no-cache"); + + // get the writer + PrintWriter out = res.getWriter(); + + try + { + LoginRenderEngine rengine = rcontext.getRenderEngine(); + rengine.render(template, rcontext, out); + } + catch (Exception e) + { + throw new RuntimeException("Failed to render template ", e); + } + + } + + public LoginRenderContext startPageContext(String skin, HttpServletRequest request, HttpServletResponse response) + { + LoginRenderEngine rengine = loginService.getRenderEngine(loginContext, request); + LoginRenderContext rcontext = rengine.newRenderContext(request); + + if (skin == null || skin.trim().length() == 0) + { + skin = ServerConfigurationService.getString("skin.default"); + } + String skinRepo = ServerConfigurationService.getString("skin.repo"); + String uiService = ServerConfigurationService.getString("ui.service"); + + String eidWording = rb.getString("userid"); + String pwWording = rb.getString("log.pass"); + String loginRequired = rb.getString("log.logreq"); + String loginWording = rb.getString("log.login"); + + rcontext.put("action", response.encodeURL(Web.returnUrl(request, null))); + rcontext.put("pageSkinRepo", skinRepo); + rcontext.put("pageSkin", skin); + rcontext.put("uiService", uiService); + rcontext.put("pageScriptPath", getScriptPath()); + rcontext.put("loginEidWording", eidWording); + rcontext.put("loginPwWording", pwWording); + rcontext.put("loginRequired", loginRequired); + rcontext.put("loginWording", loginWording); + + String eid = request.getParameter("eid"); + String pw = request.getParameter("pw"); + + if (eid == null) + eid = ""; + if (pw == null) + pw = ""; + + rcontext.put("eid", eid); + rcontext.put("password", pw); + + return rcontext; + } + + public String getLoginContext() { + return loginContext; + } + + // Helper methods + + /** + * Cleanup and redirect when we have a successful login / logout + * + * @param session + * @param tool + * @param res + * @throws IOException + */ + protected void complete(String returnUrl, Session session, Tool tool, HttpServletResponse res) throws IOException + { + // cleanup session + if (session != null) + { + session.removeAttribute(Tool.HELPER_MESSAGE); + session.removeAttribute(Tool.HELPER_DONE_URL); + session.removeAttribute(ATTR_MSG); + session.removeAttribute(ATTR_RETURN_URL); + session.removeAttribute(ATTR_CONTAINER_CHECKED); + } + + // if we end up with nowhere to go, go to the portal + if (returnUrl == null) + { + returnUrl = ServerConfigurationService.getPortalUrl(); + log.info("complete: nowhere set to go, going to portal"); + } + + // redirect to the done URL + res.sendRedirect(res.encodeRedirectURL(returnUrl)); + } + + protected String getScriptPath() + { + return "/library/js/"; + } +} diff --git a/login-tool/tool/src/webapp/WEB-INF/web.xml b/login-tool/tool/src/webapp/WEB-INF/web.xml index 2dc7e15..2fbaf2a 100644 --- a/login-tool/tool/src/webapp/WEB-INF/web.xml +++ b/login-tool/tool/src/webapp/WEB-INF/web.xml @@ -2,8 +2,8 @@ - sakai-sample-tools-login - Sakai 2 sample tools: login + xlogin + login sakai.request @@ -39,7 +39,7 @@ sakai.login - org.sakaiproject.login.tool.LoginTool + org.sakaiproject.login.tool.SkinnableLogin container /sakai-login-tool/container @@ -58,48 +58,9 @@ sakai.login.container /container/* - - + + org.sakaiproject.util.ToolListener - - - - - - - - - + diff --git a/pom.xml b/pom.xml index e28d931..de7aaff 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,12 @@ https://source.sakaiproject.org/svn/login/trunk + login-api/api login-authn-tool/tool + login-impl/impl + login-impl/pack + login-render-engine-impl/impl + login-render-engine-impl/pack login-tool/tool