Installing Sakai Winter 2006 Production

Installing Sakai

  • Create a sakai user. Sakai user's home directory is at:
    • /ucd/opt/sakai
  • Make sure that you have the following packages installed: (Installed them in /ucd/pkg/ and create soft links to /ucd/opt/)
    • ln -s /ucd/pkg/j2sdk1.4.2_10 /ucd/opt/java
    • ln -s /ucd/pkg/jdk1.5.0_06 /ucd/opt/java5
    • ln -s /ucd/pkg/maven-1.0.2 /ucd/opt/maven
    • Subversion client (You can also download the source code without having Subversion installed)
  • Getting Tomcat (Installing tomcat in /ucd/pkg)
    cd /ucd/pkg
    
    wget http://apache.forbigweb.com/tomcat/tomcat-5/v5.5.12/bin/apache-tomcat-5.5.12.tar.gz
    
    wget http://apache.forbigweb.com/tomcat/tomcat-5/v5.5.12/bin/apache-tomcat-5.5.12-compat.tar.gz
    
    tar xvzf apache-tomcat-5.5.12.tar.gz
    
    tar xvzf apache-tomcat-5.5.12-compat.tar.gz
    
    ln -s /ucd/pkg/apache-tomcat-5.5.12 /ucd/opt/tomcat
    
  • Setting environment variables
    setenv  JAVA_HOME       /ucd/opt/java
    setenv  MAVEN_HOME      /ucd/opt/maven
    setenv  CATALINA_HOME   /ucd/opt/tomcat
    
  • Configure Tomcat
    • Edit /ucd/opt/tomcat/conf/server.xml
      Replace:
      <Connector port="8080" maxHttpHeaderSize="8192"
      	maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
      	enableLookups="false" redirectPort="8443" acceptCount="100"
      	connectionTimeout="20000" disableUploadTimeout="true" />
      
      With:
      <Connector port="8080" maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
      	enableLookups="false" redirectPort="8443" acceptCount="100"
      	debug="0" connectionTimeout="20000" disableUploadTimeout="true"
      	URIEncoding="UTF-8"/>
      
      
  • Make sure that tomcat's ROOT webapp redirects to sakai
    Create this file: /ucd/opt/tomcat/webapps/ROOT/index.html
    
    With this content:
    <html>
    <head>
      <meta http-equiv="refresh" content="0;url=/portal">
    </head>
    <body>
      redirecting to /portal ...
    </body>
    </html>
    
  • Setting up Maven
    • Create repository directory:
      install_repo.sh /ucd/opt/sakai/.maven/repository
      
      Create /ucd/opt/sakai/build.properties file with the following content:
      maven.repo.remote=http://www.ibiblio.org/maven/,http://cvs.sakaiproject.org/maven/,http://horde.planetmirror.com/pub/maven/
      maven.tomcat.home=/ucd/opt/tomcat/
      
  • Getting the latest source code (As per sakai-dev list, the recommendation is to run production code off the maintenance branch)
    cd /ucd/opt/sakai
    svn co https://source.sakaiproject.org/svn/branches/sakai_2-1-1 sakai-2-1-1-maint
    
  • Building Sakai
    cd /ucd/opt/sakai/sakai_2-1-1-maint
    maven sakai
    
  • Sakai configuration
    Create a sakai folder
    mkdir /ucd/opt/tocmat/sakai
    
    Get a copy of the sakai.properties file and put it in the sakai folder
    cp /ucd/opt/sakai/sakai_2-1-1-maint/docs/sakai.properties /ucd/opt/tomcat/sakai/
    
  • Now configure the sakai.properties file
     
    # sakai.properties - documentation
    
    # identify your application server with a short name, unique among the servers in your cluster.
    # choose a server id even if you are running a single app server
    serverId=stubing1
    
    # the URL to the server, including transport, DNS name, and port, if any
    #serverUrl=http://sakai2.ucdavis.edu:8080
    serverUrl=http://sakai2.ucdavis.edu
    
    # the DNS name of the server
    serverName=stubing.ucdavis.edu
    
    # the URL to send folks to after they logout
    loggedOutUrl=/portal
    
    # the default skin name, and the URL path (relative is ok) to the collection of skins
    skin.default=default
    skin.repo=/library/skin
    
    # the database configuration (hsqldb, mysql or oracle) [ make sure to modify to match your particular setup ]
    #vendor@org.sakaiproject.service.framework.sql.SqlService=hsqldb
    #vendor@org.sakaiproject.service.framework.sql.SqlService=mysql
    vendor@org.sakaiproject.service.framework.sql.SqlService=oracle
    
    #driverClassName@javax.sql.BaseDataSource=org.hsqldb.jdbcDriver
    #driverClassName@javax.sql.BaseDataSource=com.mysql.jdbc.Driver
    driverClassName@javax.sql.BaseDataSource=oracle.jdbc.driver.OracleDriver
    
    # two hsqldb: first for in-memory (no persistence between runs), second for disk based
    #url@javax.sql.BaseDataSource=jdbc:hsqldb:.
    #url@javax.sql.BaseDataSource=jdbc:hsqldb:${sakai.home}/db/sakai.db
    #url@javax.sql.BaseDataSource=jdbc:mysql://127.0.0.1:3306/sakai?useUnicode=true&amp;characterEncoding=UTF-8
    url@javax.sql.BaseDataSource=jdbc:oracle:thin:@bricker.ucdavis.edu:1521:sakai
    
    username@javax.sql.BaseDataSource=SakaiUser
    password@javax.sql.BaseDataSource=SakaiPassword
    
    #validationQuery@javax.sql.BaseDataSource=select 1 from SYSTEM_USERS
    
    # this is good for both mysql and oracle
    validationQuery@javax.sql.BaseDataSource=select 1 from DUAL
    
    # added
    hibernate.dialect=net.sf.hibernate.dialect.Oracle9Dialect
    auto.ddl=true
    defaultTransactionIsolationString@javax.sql.BaseDataSource=TRANSACTION_READ_COMMITTED
    
    # For improved Oracle performance (from the University of Michigan)
    validationQuery@javax.sql.BaseDataSource=
    defaultTransactionIsolationString@javax.sql.BaseDataSource=
    testOnBorrow@javax.sql.BaseDataSource=false
    
    
    # enable presence display in the portal (true or false)
    display.users.present=true
    
    # smtp server for outgoing emails
    smtp@org.sakaiproject.service.framework.email.EmailService=smtp.ucdavis.edu
    
    # dns addresses used by James for incoming email
    smtp.dns.1 = 169.237.250.250
    smtp.dns.2 = 169.237.1.250
    
    # SMTP port on which James runs.  Default is 25.  Recommend running on 8025, and using a standard mailer on 25 to forward mail to Sakai.
    smtp.port = 8025
    
    # flag to enable or disable James for incoming email (true | false)
    smtp.enabled = true
    
    # upload limit per request, in megs
    content.upload.max=120
    
    # links placed on the bottom nav - set the .count to the number of items, then add each item
    bottomnav.count = 2
    bottomnav.1 = <a href="https://localhost/portal/site/!gateway">Gateway</a>
    bottomnav.2 = <a href="http://sakaiproject.org/cms" target="_blank">The Sakai Project</a>
    
    # some fill-ins for the css/vm ui (Worksite Setup, Digest Service, Email notification, Worksite Setup, Contact Support, Portal)
    ui.institution = University of California, Davis
    ui.service = Sakai@UCDavis
    
    # minutes to cache each security question in the SecurityService - set to 0 to disable caching.
    cacheMinutes@org.sakaiproject.service.legacy.security.SecurityService = 3
    
    # minutes to cache each site (site, page, tool) access in the SiteService - set to 0 to disable caching.
    cacheMinutes@org.sakaiproject.service.legacy.site.SiteService = 3
    
    # minutes to cache each user access in the UserDirectoryService - set to 0 to disable caching.(0 is the default)
    #cacheMinutes@org.sakaiproject.service.legacy.user.UserDirectoryService = 3
    
    # minutes between checks of the user cache for cleaning expired entries - set to 0 to disable caching (0 is the default)
    #cacheCleanerMinutes@org.sakaiproject.service.legacy.user.UserDirectoryService = 15
    
    # sessions expire if nothing happens in this many seconds (1 hour)
    inactiveInterval@org.sakaiproject.api.kernel.session.SessionManager=3600
    
    # presence expires if not refreshed in this many seconds
    timeoutSeconds@org.sakaiproject.service.legacy.presence.PresenceService=60
    
    # root of archive file system area - used to write archive files and to read them
    # when clustering app servers, this should be a shared network location
    ##storagePath@org.sakaiproject.service.legacy.archive.ArchiveService = ${sakai.home}/archive/
    storagePath@org.sakaiproject.service.legacy.archive.ArchiveService = /var/sakai/archive/
    
    #copyright text to appear in the bottom area of each web page.
    bottom.copyrighttext=(c) 2003, 2004, 2005 sakaiproject.org. All rights reserved.
    
    # to let the container handle login or not (set to true for single-signon type setups, false for just internal login)
    container.login = true
    
    # the file system root for content hosting's external stored files (default is null, i.e. store them in the db)
    # see the readme file (2.2.7 File Based Content Hosting) for more details
    ##bodyPath@org.sakaiproject.service.legacy.content.ContentHostingService = ${sakai.home}/content/
    bodyPath@org.sakaiproject.service.legacy.content.ContentHostingService = /var/sakai/content/
    
    # when storing content hosting's body bits in files, an optional set of folders just within the bodyPath -
    # to act as volumes to distribute the files among - a comma separate list of folders.  If left out, no volumes will be used.
    # see the readme file (2.2.7 File Based Content Hosting) for more details
    bodyVolumes@org.sakaiproject.service.legacy.content.ContentHostingService = vol1,vol2,vol3
    
    # convert to use file system rather than db
    convertToFile@org.sakaiproject.service.legacy.content.ContentHostingService = true
    
    # to disable list of appreance/icon with "edit site information" for course sites. (set as true to display only default appearance)
    #disable.course.site.skin.select=true
    
    # force all URLs out of Sakai back to Sakai to be secure, i.e. to use https, on this port.  Leave out to respond with the same transport as the request.
    #  Otherwise, the URLs will reflect the attributes of the request URL. (443 | 8443 | or any other port) [defaults to missing]
    #force.url.secure=443
    
    #to run JGroups across subnets sending multiple unicast messages
    #props@org.sakaiproject.service.legacy.event.EventTrackingService=TCP(start_port=7800):\
    #    TCPPING(initial_hosts=localhost[7800];port_range=5;timeout=3000;num_initial_members=3;up_thread=true;down_thread=true):\
    #    pbcast.NAKACK(down_thread=true;up_thread=true;gc_lag=100;retransmit_timeout=3000):\
    #    VERIFY_SUSPECT(timeout=1500;down_thread=false;up_thread=false):\
    #    pbcast.STABLE(desired_avg_gossip=20000):\
    #    pbcast.GMS(join_timeout=5000;join_retry_timeout=2000;shun=false;print_local_addr=false;down_thread=true;up_thread=true)
    
    # indicates whether users should see "Show Other Sites" twiggle in list mode of resources tool
    resources.show.all.collections=true
    
    # indicates whether users should see "Show Other Sites" twiggle in list mode of dropbox tool
    resources.show_all_collections.dropbox = false
    # indicates whether users should see "Show Other Sites" twiggle in list mode of filr picker
    resources.show_all_collections.helper = true
    # indicates whether users should see "Show Other Sites" twiggle in list mode of resources tool
    resources.show_all_collections.tool = true
    
    #the location (url) of the accessibility info
    #accessibility.url=
    
    #UCD specific parameters
    provider.kerberos.auth.login.config=/ucd/opt/java/jre/lib/security/jaas.config
    provider.kerberos.krb5.conf=/etc/krb5.conf
    provider.kerberos.showconfig=true
    secureweb.logoutURL=https://secureweb.ucdavis.edu/form-auth/logout?
    
    # stealth tools.  A stealth tool is a tool that is running in Sakai, but is not available to be added to a site in Worksite Setup.
    # A stealth tool can be added to a site using the Admin site tool.
    # List any tools to be stealthed, using their tool ids, in a comma-separated string (no spaces).
    # In this example, the rwiki tool, su tool, roster tool, and the assignment tool w/o grading are stealthed:
    #stealthTools@org.sakaiproject.api.kernel.tool.ActiveToolManager=sakai.rwiki,sakai.su,sakai.site.roster,sakai.assignment
    stealthTools@org.sakaiproject.api.kernel.tool.ActiveToolManager=sakai.site.roster,sakai.discussion,sakai.presentation
    
    
  • Creating an "archive" and "content" directory
    mkdir /ucd/opt/tomcat/sakai/content
    mkdir /ucd/opt/tomcat/sakai/archive
    
  • Make sure that you get the Oracle JDBC driver and copy it to /ucd/opt/tomcat/common/lib
  • JVM tuning
    setenv  JAVA_OPTS  " -d64 -Xmx3072m -Xms3072m -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:+UseAdaptiveSizePolicy -XX:NewSize=512m -XX:MaxPermSize=200m -XX:PermSize=64m -XX:MaxTenuringThreshold=3 -XX:CMSInitiatingOccupancyFraction=75 -XX:MinHeapFreeRatio=18"
    
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_9" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    <display-name>sakai-sample-tools-login</display-name>
    <description>Sakai 2 sample tools: login</description>

    <filter>
        <filter-name>sakai.request</filter-name>
        <filter-class>org.sakaiproject.util.RequestFilter</filter-class>
    </filter>

    <filter>
        <filter-name>sakai.request.container</filter-name>
        <filter-class>org.sakaiproject.util.RequestFilter</filter-class>
       <init-param>
            <param-name>tool.placement</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>remote.user</param-name>
            <param-value>false</param-value>
        </init-param>
    </filter>

<!-- added for distauth -->
<filter>
    	<filter-name>ucdfilter</filter-name>
    	<filter-class>edu.ucdavis.security.distauth.AuthenticationFilter</filter-class>
    
	<init-param>
          <param-name>LoginURL</param-name>
          <param-value></param-value>
      </init-param>
      <init-param>
          <param-name>InvalidCookieURL</param-name>
          <param-value></param-value>
      </init-param>
      <init-param>
          <param-name>InvalidLoginURL</param-name>
          <param-value></param-value>
      </init-param>
      <init-param>
          <param-name>InvalidTokenURL</param-name>
          <param-value></param-value>
      </init-param>
    
      <init-param>
          <param-name>InternalErrorURL</param-name>
          <param-value>Error.html</param-value>
      </init-param>
      <init-param>
          <param-name>TimeoutInterval</param-name>
          <param-value>3600</param-value>
      </init-param>
      <init-param>
          <param-name>TokenPath</param-name>
          <param-value>/afs/ucdavis.edu/common/authinfo/</param-value>
      </init-param>
      <init-param>
          <param-name>LogPath</param-name>
          <param-value></param-value>
      </init-param>
      <init-param>
          <param-name>LogFile</param-name>
          <param-value>/var/tmp/distauth.log</param-value>
      </init-param>
	  <init-param>
          <param-name>ValidateUserAgainstDB</param-name>
          <param-value>0</param-value>
      </init-param>
	  <init-param>
          <param-name>DatabaseQuery</param-name>
          <param-value></param-value>
      </init-param>
	  <init-param>
          <param-name>ProtectionLevel</param-name>
          <param-value>2</param-value>
      </init-param>
	  <init-param>
          <param-name>PassThrough</param-name>
          <param-value>0</param-value>
      </init-param>
	  <init-param>
          <param-name>LogoutURL</param-name>
          <param-value>https://secureweb.ucdavis.edu:443/form-auth/logout?</param-value>
      </init-param>
	  <init-param>
          <param-name>SendBackURL</param-name>
          <param-value>https://secureweb.ucdavis.edu:443/form-auth/sendback?</param-value>
      </init-param>
  </filter>


    <filter-mapping>
        <filter-name>sakai.request</filter-name>
        <servlet-name>sakai.login</servlet-name>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
        <dispatcher>INCLUDE</dispatcher>
    </filter-mapping>

    <filter-mapping>
        <filter-name>sakai.request.container</filter-name>
        <servlet-name>sakai.login.container</servlet-name>
        <dispatcher>REQUEST</dispatcher>
    </filter-mapping>

    <servlet>
        <servlet-name>sakai.login</servlet-name>
        <servlet-class>org.sakaiproject.tool.login.LoginTool</servlet-class>
        <init-param>
            <param-name>container</param-name>
            <param-value>/sakai-login/container</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet>
        <servlet-name>sakai.login.container</servlet-name>
        <servlet-class>org.sakaiproject.tool.login.ContainerLogin</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

	<!-- map the container login, and then cause it to trigger container authentication -->
    <servlet-mapping>
        <servlet-name>sakai.login.container</servlet-name>
        <url-pattern>/container/*</url-pattern>
    </servlet-mapping>
	
<!-- Turn distauth off/on -->
	
	<filter-mapping>
  	<filter-name>ucdfilter</filter-name>
  		 <servlet-name>sakai.login.container</servlet-name>
	 	<dispatcher>REQUEST</dispatcher>
	</filter-mapping>
	

    <listener>
        <listener-class>org.sakaiproject.util.ToolListener</listener-class>
    </listener>

	<!-- to add a user to this BASIC auth tomcat container authentication, add lines to your tomcat/conf/tomcat-users.xml:
    		*** Enable this only if using Tomcat's built-in BASIC auth
	    <tomcat-users>
	        <role rolename="sakaiuser"/>
	        <user username="NAME" password="PW" roles="sakaiuser"/>
	    </tomcat-users>
	among the other definitions there -->

    <!-- Define a Security Constraint on this Application
    		*** Enable this only if using Tomcat's built-in BASIC auth
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>container</web-resource-name>
            <url-pattern>/container/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>sakaiuser</role-name>
        </auth-constraint>
    </security-constraint>
	*** -->

    <!-- Define the Login Configuration for this Application
    		*** Enable this only if using Tomcat's built-in BASIC auth
    <login-config>
        <auth-method>BASIC</auth-method>
        <realm-name>Sakai Users</realm-name>
    </login-config>
	*** -->

    <!-- Security roles referenced by this web application
    		*** Enable this only if using Tomcat's built-in BASIC auth
    <security-role>
        <description>
            The role that is required to log in to Sakai
        </description>
        <role-name>sakaiuser</role-name>
    </security-role>
	*** -->

</web-app>



  • Enable Kerberos Provider, for WebDav support
    • Request a krb5.keytab file for the server you are running (service ticket). You may do this by following these directions:
    • Place the attached krb5.conf file in the location where specified in the above sakai.properties file, parameter
      provider.kerberos.krb5.conf.
      For example, the location for the krb5.conf file in the above sakai.properties is at /etc/krb5.conf.

The page for requesting service principals:
https://computingaccounts.ucdavis.edu/cgi-bin/kerberos/index.cgi
Request

Unknown macro: {service/host}

.ucdavis.edu with service as HTTP (in all caps).
For example, felix is set up as HTTP/felix.ucdavis.edu.

    • Create a new file with the following contents. Name the file jaas.config.
      KerberosAuthentication {
         com.sun.security.auth.module.Krb5LoginModule required
         debug=false
         storekey=false
         clearPass=false
         useTicketCache=false
         useKeyTab=true
         keyTab="/etc/krb.keytab"; 
         };
      
    • Copy the above 3 files into your [jdk-src]/jre/lib/security/ directory. Note, in the jaas.config file you can identify where your keytab file resides if one wants to put it somewhere else. Also note the quotes around the file path.
    • Make sure that one of each providers is enalbed. In some cases the SampleGroupProvider is commented out.
    • Enable the Kerberos Provider by first commenting out the SampleUserDirectoryProvider in the [sakai-src]/providers/src/component/src/webapp/WEB-INF/components.xml file. See below for the snippet to uncomment (REMEMBER TO COMMENT OUT THE SAMPLEUSERDIRECTORYPROVIDER!!!!):
      <!-- below is orinally commented out, see KerberosDirectoryProvider documentation for further info -->
      	<bean id="org.sakaiproject.service.legacy.user.UserDirectoryProvider"
      			class="org.sakaiproject.component.kerberos.user.KerberosUserDirectoryProvider"
      			init-method="init"
      			destroy-method="destroy"
      			singleton="true">
      		<property name="logger"><ref bean="org.sakaiproject.service.framework.log.Logger"/></property>
      		<property name="domain"><value>ucdavis.edu</value></property>
      		<property name="loginContext"><value>KerberosAuthentication</value></property>
      		<property name="requireLocalAccount"><value>true</value></property>
      		<property name="knownUserMsg"><value>Integrity check on decrypted field failed</value></property>
      	</bean>
      
    • Uncomment the KerberosProvider in the [sakai-src]/providers/component/project.xml file. See below:
                   <!-- Needed for the Kerberos Provider, is commented by default --> 
      		<dependency>
      			<groupId>sakaiproject</groupId>
      			<artifactId>sakai-kerberos-provider</artifactId>
      			<version>${sakai.version}</version>
      			<properties>
      				<war.bundle>true</war.bundle>
      			</properties>
      		</dependency>
      
    • Download current patch release of KerberosUserDirectoryProvider.java. Replace the "stock" kerberos provider with this one. The provider code is attached, and enables caching.
    • Rebuild sakai for changes to take effect
  • Enable logout functionality of Secureweb in Sakai
    • Copy the contents of the following LoginTool.java code into the [saka-src/login/login/src/java/org/sakaiproject/tool/login directory. This file will replace the default code shipped with Sakai. Changes were made to the "complete" method to enable logout properly via DistAuth. Below is the entire code, or it may be found in SVN @ https://mware.ucdavis.edu/svn/sakai/distauth/trunk/LogoutSecureWebFunctionality/LoginTool.java
      /**********************************************************************************
       *
       * $Header: /cvs/sakai2/login/login/src/java/org/sakaiproject/tool/login/LoginTool.java,v 1.17 2005/06/04 03:14:22 ggolden.umich.edu Exp $
       *
       ***********************************************************************************
       *
       * Copyright (c) 2005 The Regents of the University of Michigan, Trustees of Indiana University,
       *                  Board of Trustees of the Leland Stanford, Jr., University, and The MIT Corporation
       * 
       * Licensed under the Educational Community License Version 1.0 (the "License");
       * By obtaining, using and/or copying this Original Work, you agree that you have read,
       * understand, and will comply with the terms and conditions of the Educational Community License.
       * You may obtain a copy of the License at:
       * 
       *      http://cvs.sakaiproject.org/licenses/license_1_0.html
       * 
       * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
       * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
       * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
       * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
       * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
       *
       **********************************************************************************/
      
      package org.sakaiproject.tool.login;
      
      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.api.common.authentication.Authentication;
      import org.sakaiproject.api.common.authentication.AuthenticationException;
      import org.sakaiproject.api.common.authentication.Evidence;
      import org.sakaiproject.api.common.authentication.cover.AuthenticationManager;
      import org.sakaiproject.api.kernel.session.Session;
      import org.sakaiproject.api.kernel.session.cover.SessionManager;
      import org.sakaiproject.api.kernel.tool.Tool;
      import org.sakaiproject.service.framework.config.cover.ServerConfigurationService;
      import org.sakaiproject.util.IdPwEvidence;
      import org.sakaiproject.util.LoginUtil;
      import org.sakaiproject.util.web.Web;
      
      /**
       * <p>
       * Login tool for Sakai. Works with the ContainerLoginTool servlet to offer container or internal login.
       * </p>
       * <p>
       * 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.
       * </p>
       * 
       * @author University of Michigan, Sakai Software Development Team
       * @version $Revision: 666 $
       */
      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";
      
      	/**
      	 * 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();
      		System.out.println(option + "=this logout option*****************");
      		// 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
      			LoginUtil.logout();
      
      			complete(returnUrl, null, tool, res,"logout");
      			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;
      
      					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 = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"
      				+ "<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\" xml:lang=\"en\">"
      				+ "  <head>"
      				+ "    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />"
      				+ "    <link href=\"SKIN_ROOT/tool_base.css\" type=\"text/css\" rel=\"stylesheet\" media=\"all\" />"
      				+ "    <link href=\"SKIN_ROOT/DEFAULT_SKIN/tool.css\" type=\"text/css\" rel=\"stylesheet\" media=\"all\" />"
      				+ "    <meta http-equiv=\"Content-Style-Type\" content=\"text/css\" />"
      				+ "    <title>Sakai</title>"
      				+ "    <script type=\"text/javascript\" language=\"JavaScript\" src=\"/library/js/headscripts.js\"></script>"
      				+ "  </head>"
      				+ "  <body onload=\" setFocus(focus_path);parent.updCourier(doubleDeep, ignoreCourier);\">"
      				+ "<script type=\"text/javascript\" language=\"JavaScript\">" + "  focus_path = [\"eid\"];" + "</script>";
      
      		final String tailHtml = "</body></html>";
      
      		final String loginHtml = "<table border=\"3\" cellpadding=\"0\" cellspacing=\"0\" width=\"470\" style =\"margin-right:auto;margin-left:auto;margin-top:5em;background-color:#fff;font-family:helvetica,arial,sans-serif\">"
      				+ "	<tr>"
      				+ "		<td>"
      				+ "			<table bgcolor=\"#FFFFFF\" border=\"0\" cellpadding=\"5\" cellspacing=\"0\">"
      				+ "				<tr>"
      				+ "					<td colspan=\"2\" bgcolor=\"#11375E\" align=\"left\">"
      				+ "						<b style=\"color:#ffffff; font-size: 12pt\">"
      				+ "							Login Required"
      				+ "						</b>"
      				+ "					</td>"
      				+ "				</tr>"
      				+ "				<tr>"
      				+ "					<td>"
      				+ "						<img src=\"SKIN_ROOT/DEFAULT_SKIN/images/logo_inst.gif\" border=\"0\" hspace=\"0\" vspace=\"0\" alt=\"\" />"
      				+ "					</td>"
      				+ "					<td valign=\"top\">"
      				+ "						<form method=\"post\" action=\"ACTION\" enctype=\"application/x-www-form-urlencoded\">"
      				+ "							<table border=\"0\">"
      				+ "								<tr>"
      				+ "									<td style=\"font-weight:bold;\">"
      				+ "										EID"
      				+ "									</td>"
      				+ "									<td>"
      				+ "										<input name=\"eid\" id=\"eid\"  type=\"text\" style =\"width: 10em\" />"
      				+ "									</td>"
      				+ "								</tr>"
      				+ "								<tr>"
      				+ "									<td style=\"font-weight:bold;\">"
      				+ "										PW"
      				+ "									</td>"
      				+ "									<td>"
      				+ "										<input name=\"pw\" type=\"password\" style =\"width: 10em\" />"
      				+ "									</td>"
      				+ "								</tr>"
      				+ "								<tr>"
      				+ "									<td colspan=\"2\" align=\"right\">"
      				+ "										<input name=\"submit\" type=\"submit\" id=\"submit\" value=\"Log in\" style=\"float:right\" />"
      				+ "                                        MSG"
      				+ "									</td>"
      				+ "								</tr>"
      				+ "							</table>"
      				+ "						</form>"
      				+ "					</td>"
      				+ "				</tr>"
      				+ "			</table>" + "		</td>" + "	</tr>" + "</table>";
      
      		// 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));
      
      		String eidWording = null;
      		String pwWording = null;
      
      		// read my configuration
      		if (tool != null)
      		{
      			eidWording = tool.getRegisteredConfig().getProperty("eid");
      			pwWording = tool.getRegisteredConfig().getProperty("pw");
      		}
      
      		if (eidWording == null) eidWording = "eid";
      		if (pwWording == null) pwWording = "pw";
      
      		if (!fragment)
      		{
      			// set our response type
      			res.setContentType("text/html; charset=UTF-8");
      		}
      
      		String defaultSkin = ServerConfigurationService.getString("skin.default");
      		String skinRoot = ServerConfigurationService.getString("skin.repo");
      
      		// 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);
      			out.println(head);
      		}
      
      		// if we are in helper mode, there might be a helper message
      		if (session.getAttribute(Tool.HELPER_MESSAGE) != null)
      		{
      			out.println("<p>" + session.getAttribute(Tool.HELPER_MESSAGE) + "</p>");
      		}
      
      		// 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);
      
      		// add the default skin
      		html = html.replaceAll("DEFAULT_SKIN", defaultSkin);
      		html = html.replaceAll("SKIN_ROOT", skinRoot);
      
      		// write a message if present
      		String msg = (String) session.getAttribute(ATTR_MSG);
      		if (msg != null)
      		{
      			html = html.replaceAll("MSG", "<div class=\"chefAlertBox\">Alert: " + msg + "</div>");
      			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").trim();
      		String pw = req.getParameter("pw").trim();
      
      		// 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, "login 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,"cancel");
      		}
      
      		// submit
      		else
      		{
      			Evidence e = new IdPwEvidence(eid, pw);
      
      			// authenticate
      			try
      			{
      				if ((eid.length() == 0) || (pw.length() == 0))
      				{
      					throw new AuthenticationException("missing required fields");
      				}
      
      				Authentication a = AuthenticationManager.authenticate(e);
      
      				// login the user
      				LoginUtil.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,"auth");
      			}
      			catch (AuthenticationException ex)
      			{
      				session.setAttribute(ATTR_MSG, "invalid login");
      
      				// 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, String reply) throws IOException
      	{
      		//added info by Scott Amerson to include logout url for secureweb
      				
      		
      		// 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);
      		}
      
      		// redirect to the done URL
      		if (reply.equals("logout"))
      		{
      		String LogOutURL = ServerConfigurationService.getString("secureweb.logoutURL", "");
      		res.sendRedirect(LogOutURL + res.encodeRedirectURL(returnUrl));	
      		}
      		else
      		{	
      		res.sendRedirect(res.encodeRedirectURL(returnUrl));
      		}
      	}
      
      	
      
      }
      
      /**************************************************************************************************************************************************************************************************************************************************************
       * $Header: /cvs/sakai2/login/login/src/java/org/sakaiproject/tool/login/LoginTool.java,v 1.17 2005/06/04 03:14:22 ggolden.umich.edu Exp $
       *************************************************************************************************************************************************************************************************************************************************************/
      
    • Rebuild Sakai
  • Enabling SSL on Tomcat, so that WebDav passwords are encrypted
    **Create Certificate
    cd /ucd/opt/java/bin
    keytool -genkey -alias tomcat -keyalg RSA -keystore /etc/keystore -storepass mysecretpass
    
    **Update Tomcat's server.xml to enable SSL
    update the SSL Connection to have the following attributes:
    SSLProtocol, keystoreType, keystoreFile, and keystorePass (see below)
    <Connector port="6443" 
             maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
             enableLookups="false" disableUploadTimeout="true"
             acceptCount="100" debug="0" scheme="https" secure="true"
             clientAuth="false" sslProtocol="TLS" keystoreType="JKS"
             keystoreFile="/etc/.keystore" keystorePass="mysecretpass"/>
    
    **Restart Tomcat with the given port number specified. You can choose to turn off port 8080, or keep it open and do redirection via Apache.

    System Related Setup

  • Tomcat start on boot
    in /etc/rc.d/ edit rc.local
    ========================================================================
    #!/bin/sh
    #
    # This script will be executed *after* all the other init scripts.
    # You can put your own initialization stuff in here if you don't
    # want to do the full Sys V style init stuff.
    
    touch /var/lock/subsys/local
    
    # Source function library.
    . /etc/rc.d/init.d/functions
    
    action "Starting Tomcat: " su -c '/etc/rc.d/tomcat.sh' sakai
    ========================================================================
    
    
    in /etc/rc.d/ create the tomcat.sh file
    ========================================================================
    #!/bin/sh
    
    /ucd/opt/tomcat/bin/startup.sh
    ========================================================================
    
  • Configure iptables so that we can use/run sakai's mail system as the sakai user
    # Generated by iptables-save v1.2.8 on Thu Sep  9 14:43:05 2004
    *nat
    :PREROUTING ACCEPT [510:80231]
    :POSTROUTING ACCEPT [12:2548]
    :OUTPUT ACCEPT [12:2548]
    -A PREROUTING -i eth0 -p tcp -m tcp --dport 25 -j REDIRECT --to-ports 8025
    -A PREROUTING -i eth0 -p tcp -m tcp --dport 80 -j REDIRECT --to-ports 8080
    COMMIT
    *filter
    :INPUT ACCEPT [0:0]
    :FORWARD ACCEPT [0:0]
    :OUTPUT ACCEPT [74067:26067969]
    :RH-Firewall-1-INPUT - [0:0]
    -A INPUT -j RH-Firewall-1-INPUT
    -A FORWARD -j RH-Firewall-1-INPUT
    -A RH-Firewall-1-INPUT -i lo -j ACCEPT
    -A RH-Firewall-1-INPUT -p icmp -m icmp --icmp-type 255 -j ACCEPT
    -A RH-Firewall-1-INPUT -p esp -j ACCEPT
    -A RH-Firewall-1-INPUT -p ah -j ACCEPT
    -A RH-Firewall-1-INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
    -A RH-Firewall-1-INPUT -p tcp -m state --state NEW -m tcp -s 169.237.0.0/16 --dport 22 -j ACCEPT
    -A RH-Firewall-1-INPUT -p udp -m state --state NEW -m udp -s 169.237.0.0/16 -j ACCEPT
    -A RH-Firewall-1-INPUT -p tcp -m state --state NEW -m tcp --dport 8025 -j ACCEPT
    -A RH-Firewall-1-INPUT -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT
    -A RH-Firewall-1-INPUT -p tcp -m state --state NEW -m tcp --dport 8080 -j ACCEPT
    -A RH-Firewall-1-INPUT -j REJECT --reject-with icmp-host-prohibited
    COMMIT
    # Completed on Thu Sep  9 14:43:05 2004
    
    
  • Here is some more information on setting up and configuring James