/*
* Environment.java
*
* Created on May 16, 2007, 11:59 AM
*
* Source for the Colleague::Environment class.
*
* Copyright 2007-2011 Jakim Friant
*
* This file is part of ResetPassword.
*
* ResetPassword is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ResetPassword is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ResetPassword. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.cfcc.colleague;
import asjava.uniclientlibs.*;
import asjava.uniobjects.*;
import java.text.DateFormat;
import java.text.FieldPosition;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.ListIterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.acegisecurity.providers.ldap.LdapShaPasswordEncoder;
import org.cfcc.DuplicateRecordException;
import org.cfcc.LockedAccountException;
import org.cfcc.SystemUnavailableException;
import org.cfcc.utils.Validate;
/**
* Manages the connection to a Colleague R18 Environment.
* @author jfriant
*/
public class Environment extends Account {
/** Field location of the saved question in the local params file */
public static final int LOC_X810_P_QUESTION = 1;
/** Field location of the answer in local params file */
public static final int LOC_X810_P_ANSWER = 2;
/** List of possible Security Questions */
public static final String[] security_questions = {
"What is your Mother's Maiden Name",
"What is your Place of Birth",
"What is your Father's Middle Name",
"What is your Favorite Holiday",
"What was your First Pet",
"What was your First Car",
"What was your Elementary School"
};
public static final String[] security_question_examples = {
"Smith",
"Chicago",
"John",
"Christmas",
"Rover",
"Ford",
"Ogden"
};
/** Length of the answer allowed for the security question */
public static final int SECRET_LEN = 35;
/** The preferred address foreign key in PERSON */
public static final int LOC_PREF_ADDR = 6;
/** Field location of the SSN in the PERSON file */
public static final int LOC_SSN = 8;
/** Field location of the birth date in the PERSON file */
public static final int LOC_BIRTHDATE = 14;
/** Field number of the where used field in PERSON */
public static final int LOC_WHERE_USED = 31;
/** Field location of the USER4 field in the PERSON file, stores the answer */
public static final int LOC_USER4 = 98;
/** Field location of the USER5 field in the PERSON file, stores the question */
public static final int LOC_USER5 = 101;
/** Field number of the user id in PERSON.PIN */
public static final int LOC_PERSON_PIN_USER_ID = 8;
/** Field number of the zip code in ADDRESS */
public static final int LOC_ZIP = 1;
/** Length of the zip code without the extended portion. */
private static final int ZIP_LEN = 5;
/** Field location of the STAFF.OFFICE.CODE in the STAFF file */
public static final int LOC_STAFF_OFFICE_CODE = 6;
private static final String[][] WHERE_USED = {
{"FACULTY", "ou=Faculty,ou=Employees"},
{"EMPLOYES", "ou=Staff,ou=Employees"},
{"STUDENTS", "ou=Students"},
{"APPLICANTS", "ou=Students"}
};
/** the Default group for someone with no group */
private static final String DEFAULT_CONTEXT = "ou=Students";
/** the person file pointer */
protected Person person = null;
/** the address file object */
protected Address address = null;
/** The org.entity file object */
public OrgEntity org_entity;
/** The org.entity.env file pointer */
protected OrgEntityEnv org_entity_env;
/** The connection to the staff file */
private Staff staff;
/** The connection to the local parameter file */
private LocalParams local_params;
/** The ID of the current record */
private String person_id = "";
/** The first four digits of the SSN on record */
public String ssn_four = "";
/** The zip code from the preferred address */
public String zip = "";
/** The birth date from the current record */
public String birth_date = "";
/** The SECRET required when resetting the checkPassword (initially blank) */
public String secret = "";
/** The corresponding security question for the secret */
public String question = "";
/** The security question sample text */
public String example = "";
/** Name of the parameter file (PERSON or custom) */
private String local_params_name;
/** Stores the path in case we need to reconnect */
private String environment_path;
/**
* Class Constructor. Sets the username and checkPassword that will be used to
* connect to the UniData account.
*
* @param s the server DNS name or IP address
* @param u the username
* @param p the checkPassword
*/
public Environment(String s, String u, String p) {
super(s, u, p);
local_params_name = "PERSON";
}
/**
* Sets the host address, username, and checkPassword used establish a connection
* to the database. Sets the name of the database file that holds the
* records containing the security question and answer.
*
* @param s the server DNS name or IP address
* @param u the username used to login to the database
* @param p the checkPassword
* @param local_params the database file that stores the security questions
*/
public Environment(String s, String u, String p, String local_params) {
super(s, u, p);
local_params_name = local_params;
}
public void reconnect() throws UniException {
this.connect(environment_path);
}
/**
* Returns the appropriate record from ORG.ENTITY.ENV for the given
* PERSON ID. Replaces the Colleague subroutine that does the same thing.
* @param person_id The Colleague PERSON ID
* @return an OrgEntityEnv object with the data
* @throws asjava.uniobjects.UniFileException when the record is not found.
*/
public OrgEntityEnv getOEE(String person_id) throws UniFileException, UniSessionException {
org_entity.setId(person_id);
org_entity_env.setId(org_entity.org_entity_env.get());
username = org_entity_env.username.get();
return org_entity_env;
}
/**
* Creates the ColFile objects, linking them to the active session connection.
*/
public void openAll() {
address = new Address(uSession);
local_params = new LocalParams(uSession, local_params_name);
org_entity = new OrgEntity(uSession);
org_entity_env = new OrgEntityEnv(uSession);
person = new Person(uSession);
staff = new Staff(uSession);
}
/**
* Close the colleague files when the session is finished.
*/
public void close() {
address.close();
local_params.close();
org_entity.close();
org_entity_env.close();
person.close();
staff.close();
}
/**
* Reads the person record and sets the data members. Use to get all the
* values for user verification at one time.
*
* @param per_id PERSON record id
* @return true if all the records were read without an error
* @throws LockedAccountException is generated if a database record lock is found
*/
public boolean loadData(String per_id) throws LockedAccountException {
boolean success = false;
person_id = per_id;
person.setId(per_id);
String result = "";
try {
// Read the birthdate field
birth_date = person.birth_date.get().replace('/', '-');
} catch (UniSessionException ex) {
Logger.getLogger(Environment.class.getName()).log(Level.SEVERE, "Error reading birthdate", ex);
birth_date = "Error: " + ex;
} catch (UniFileException ex) {
Logger.getLogger(Environment.class.getName()).log(Level.SEVERE, "Error reading birthdate", ex);
birth_date = "Error: " + ex;
}
// read the SSN field, store a blank string if nothing is there
try {
result = person.ssn.get();
if (result.length() > 0) {
ssn_four = result.substring(0, 3).toString()
+ result.substring(4, 5).toString();
} else {
ssn_four = "";
}
} catch (StringIndexOutOfBoundsException e) {
System.err.println("ResetPassword: SSN Error for Person ("
+ person_id + "):" + e);
} catch (UniFileException ex) {
Logger.getLogger(Environment.class.getName()).log(Level.SEVERE, "Error reading SSN", ex);
ssn_four = "Error: " + ex;
} catch (UniSessionException ex) {
Logger.getLogger(Environment.class.getName()).log(Level.SEVERE, "Error reading SSN", ex);
ssn_four = "Error: " + ex;
}
// get the zip from the preferred address
try {
String pref_addr = person.pref_addr.get();
address.setId(pref_addr);
zip = address.zip.get().substring(0, ZIP_LEN);
} catch (UniFileException ex) {
Logger.getLogger(Environment.class.getName()).log(Level.SEVERE, "Error reading zip", ex);
zip = "Error: " + ex;
} catch (UniSessionException ex) {
Logger.getLogger(Environment.class.getName()).log(Level.SEVERE, "Error reading zip", ex);
zip = "Error: " + ex;
}
success = true;
loadSecurityQuestion(person_id);
return success;
}
/**
* Load, into public data members, the security question and secret that was
* stored for the given person. Also determines where the question is stored
* based on whether the calling code provided a local params filename or not,
* if not then we'll assume that the data is stored in the PERSON file.
*
* @param person_id the Colleague PERSON ID of the record to load
* @throws LockedAccountException when the record has been administratively locked
* @see loadData
*/
public void loadSecurityQuestion(String person_id) throws LockedAccountException {
secret = "";
question = "";
example = "";
String err_msg = "(track_id={0}) Error loading security question: {1}";
// don't bother with an empty record
if (!person_id.equals("")) {
try {
local_params.setId(person_id);
secret = local_params.answer.get();
if (!secret.equals("")) {
// read the field from the local params file with the Security Question
question = local_params.question.get();
// Older records could have a blank question, so use the default
if (question.equals("")) {
question = Environment.security_questions[0];
} else {
if (question.equals("LOCKED")) {
throw new LockedAccountException();
}
}
int which = 0;
for (int i = 0; i < Environment.security_questions.length; i++) {
if (Environment.security_questions[i].equals(question)) {
which = i;
break;
}
}
example = Environment.security_question_examples[which];
}
} catch (UniFileException ex) {
String vars[] = {person_id, ex.toString()};
Logger.getLogger(Environment.class.getName()).log(Level.WARNING, err_msg, vars);
} catch (UniSessionException ex) {
String vars[] = {person_id, ex.toString()};
Logger.getLogger(Environment.class.getName()).log(Level.WARNING, err_msg, vars);
}
}
}
/**
* Calculates the LDAP OU Context for this person. Implements the same logic
* used in the subroutine "XS.CALC.LDAP.CONTEXT.X810", which has been
* customized for CFCC records.
*
* From PERSON WHERE.USED
*
* @param person_id the PERSON record id
* @return the context for that person record, or an empty string
* if there was an error
*/
public String getContext(String person_id) {
String final_context = DEFAULT_CONTEXT;
String context = "";
try {
person.setId(person_id);
for (int i = 0; i < WHERE_USED.length; i++) {
ListIterator<?> result = person.where_used.get().listIterator();
while (result.hasNext()) {
String where_used = (String) result.next();
//System.out.println("Environment::getContext(" + person_id + "): where_used=" + where_used + " == " + WHERE_USED[i][0]);
if (where_used.equals(WHERE_USED[i][0])) {
context = WHERE_USED[i][1];
break;
}
}
if (!context.equals("")) {
final_context = context;
break;
}
}
} catch (UniFileException ex) {
int status = uSession.status();
Logger.getLogger(Environment.class.getName()).log(Level.WARNING, "status=" + status, ex);
} catch (UniSessionException ex) {
int status = uSession.status();
Logger.getLogger(Environment.class.getName()).log(Level.WARNING, "status=" + status, ex);
}
System.out.println("Environment::getContext(" + person_id + "): final context=" + context);
return final_context;
}
/**
* Saves the SECRET to to the local params file. The SECRET is checked to
* make sure it is a string of up to SECRET_LEN in length and is then made
* upper case to avoid case errors later on. Finally it is encrypted as a
* Base64 encoded SHA1 hash. The block of code that does the field write
* is synchronized and will be attempted 3 times if the record is locked.
*
* @param person_id the PERSON record ID
* @param question_in the question to save that goes along with the secret
* @param secret_in the secret string entered by the user
* @throws SystemUnavailableException If the SHA1 algorithm is not
* supported by the JVM.
*/
public void setSecret(String person_id, String question_in, String secret_in)
throws SystemUnavailableException, UniFileException, UniSessionException {
LdapShaPasswordEncoder pwd = new LdapShaPasswordEncoder();
String secret_hash = pwd.encodePassword(secret_in.toUpperCase(), null);
local_params.setId(person_id);
local_params.question.set(question_in);
local_params.answer.set(secret_hash);
}
/**
* Compares the hashed input with the hashed secret in the database.
*
* @param secret_in the input string from the web form
* @return true if the hashes match or if there is no secret set yet
* @throws SystemUnavailableException If the SHA1 hash is not supported
* by the JVM.
*/
public boolean checkSecret(String secret_in)
throws SystemUnavailableException {
boolean found_match = false;
// if the user has not set a secret answer yet, then we'll let them
// pass
if (secret.equals("")) {
found_match = true;
} else {
// otherwise compare the hashes
secret_in = Validate.sanitize(secret_in, SECRET_LEN);
LdapShaPasswordEncoder pwd = new LdapShaPasswordEncoder();
found_match = pwd.isPasswordValid(secret, secret_in.toUpperCase(), null);
}
return found_match;
}
/**
* Find any office codes in the STAFF file for the
* user.
* @param per_id The staff record ID
* @throws asjava.uniobjects.UniSessionException when there is a session exception when trying to connect or open a file
* @throws asjava.uniobjects.UniFileException when there is a read error
* @return a string with an office codes present for the given user
*/
public ArrayList<String> getOfficeCodes(String per_id)
throws UniSessionException, UniFileException {
person_id = per_id; // save for later if needed
ArrayList<String> codes = new ArrayList<String>();
staff.setId(per_id);
try {
// Read the STAFF.OFFICE.CODE field
ListIterator<String> result = staff.office_code.get().listIterator();
while (result.hasNext()) {
codes.add(result.next());
}
} catch (UniFileException e) {
ListIterator<?> result_list = person.where_used.get().listIterator();
while (result_list.hasNext()) {
String where_used = (String) result_list.next();
// these strings come from the S.CALC.LDAP.CONTEXT subroutine
if (where_used.equals("FACULTY") || where_used.equals("EMPLOYES")) {
codes.add("WFS");
break;
}
}
System.err.println("[WARNING] Using default WFS code for " + per_id + ": " + e);
}
return codes;
}
/**
* Sets the checkPassword change date to the current time in milliseconds.
* That way WebAdvisor won't prompt the user to change their checkPassword a
* second time.
*
* The date and time must be converted to UniData format for example
* 5/17/07 = Field 031: 14382
* 16:33 = Field 032: 59606
*
* Also resets the WebAdvisor invalid logins counter and date to zero,
* just in case the user has had too many failed logins before they try to
* reset their checkPassword.
* @param person_id the ORG.ENTITY.ENV record ID
*/
public void setPasswordDate(String person_id) {
String date_now = "";
String time_now = "";
SimpleDateFormat df = new java.text.SimpleDateFormat("MM/dd/yy");
SimpleDateFormat tf = new SimpleDateFormat("hh:mm");
Date now = new Date();
FieldPosition fp = new FieldPosition(DateFormat.DATE_FIELD);
try {
date_now = uSession.iconv(df.format(now, new StringBuffer(""), fp).toString(), "D2/MDY").toString();
} catch (UniStringException ex) {
System.err.println("Unable to convert the date: " + ex);
}
try {
time_now = uSession.iconv(tf.format(now, new StringBuffer(""), fp).toString(), "MT:").toString();
} catch (UniStringException ex) {
System.err.println("Unable to convert the time: " + ex);
}
// don't bother with a blank record
if (person_id.equals("")) {
return;
}
// Get the record id
System.out.println("setPasswordDate " + org_entity.toString());
org_entity.setId(person_id);
try {
org_entity_env.setId(org_entity.org_entity_env.get());
org_entity_env.password_chg_date.set(date_now);
org_entity_env.password_chg_time.set(time_now);
/* also reset the failed login fields */
org_entity_env.no_invalid_logins.set("0");
org_entity_env.invalid_login_date.set("0");
org_entity_env.invalid_login_time.set("0");
org_entity_env.unlockRecord();
} catch (UniFileException e) {
System.err.println("ResetPassword: ORG.ENTITY.ENV Record locked for " + org_entity_env.getId());
} catch (UniSessionException e) {
System.err.println("ResetPassword: ORG.ENTITY.ENV Session errror for " + org_entity_env.getId());
}
}
/**
* Save the previous checkPassword in the LOCAL_PARAMS file. Only works if the file
* is truly a PERSON co-file and not using USER fields in person.
* @param person_id is the 7-digit PERSON id
* @param passwd is the encrypted checkPassword string
* @throws SystemUnavailableException if the UniData connection fails
* @throws UniFileException if there is an error writing the record
*/
public void setPasswordHistory(String person_id, String passwd) throws SystemUnavailableException, UniFileException, UniSessionException {
local_params.setId(person_id);
local_params.setPassword(passwd);
}
/**
* Find out if the checkPassword has been used before.
* @param person_id the record ID to check
* @param passwd the new checkPassword used to compare against the old one
* @return true if the checkPassword matches one in the history
*/
public boolean checkPasswordHistory(String person_id, String passwd) {
boolean result = false;
try {
LdapShaPasswordEncoder enc = new LdapShaPasswordEncoder();
local_params.setId(person_id);
String hist = local_params.getPassword();
if (!hist.equals("")) {
result = enc.isPasswordValid(hist, passwd, null);
}
} catch (UniFileException ex) {
String vars[] = {person_id, ex.toString()};
String msg = "(track_id={0}) No password history found: {1}";
Logger.getLogger(Environment.class.getName()).log(Level.WARNING, msg, vars);
} catch (UniSessionException ex) {
String vars[] = {person_id, ex.toString()};
String msg = "(track_id={0}) No password history found: {1}";
Logger.getLogger(Environment.class.getName()).log(Level.WARNING, msg, vars);
} catch (SystemUnavailableException ex) {
String vars[] = {person_id, ex.toString()};
String msg = "(track_id={0}) system error when retrieving password history: {1}";
Logger.getLogger(Environment.class.getName()).log(Level.SEVERE, msg, vars);
}
return result;
}
/**
* Return a string with the person ID after running a select on the PERSON file.
* The selection is expected to run against an Indexed field and not take long.
* @param ssn a string of numbers (dashes are ok)
* @param strict raise an exception if more than one record is returned.
* @return a string with the person ID
* @throws UniSelectListException
* @throws UniSessionException
* @throws UniCommandException
* @throws DuplicateRecordException if multiple records are returned by the select and strict is true
*/
public String getPersonBySsn(String ssn, boolean strict) throws UniSelectListException, UniSessionException, UniCommandException, DuplicateRecordException {
String person_id = new String("");
ssn = Validate.formatSsn(ssn);
String cmd = "SELECT PERSON WITH SSN EQ '" + ssn + "' TO 1";
//System.out.println("[DEBUG] " + cmd);
UniCommand com1 = uSession.command();
com1.setCommand(cmd);
com1.exec();
if (com1.status() == UniObjectsTokens.UVS_COMPLETE) {
UniSelectList uSelect = uSession.selectList(1);
UniDynArray select_results = uSelect.readList();
//System.out.println("[DEBUG] " + select_results.toString());
if (strict && select_results.length() > 1) {
throw new DuplicateRecordException(select_results.toString());
} else {
person_id = select_results.extract(1).toString();
}
}
return person_id;
}
/**
* Return the username from ORG.ENTITY.ENV if the last name matches.
* @param person_id a string with the 7-digit record key
* @param last_name case-insensitive string that must match what is on file
* @return a string with the user name or an empty string
*/
public String getUsername(String person_id, String last_name) {
String username = "";
person.setId(person_id);
try {
String expected_last = person.last_name.get().toLowerCase();
if (expected_last.equals(last_name.toLowerCase())) {
org_entity.setId(person_id);
org_entity_env.setId(org_entity.org_entity_env.get());
username = org_entity_env.username.get();
}
} catch (UniFileException e) {
System.err
.println("ResetPassword: ORG.ENTITY.ENV Record locked for "
+ org_entity_env.getId());
} catch (UniSessionException e) {
System.err
.println("ResetPassword: ORG.ENTITY.ENV Session errror for "
+ org_entity_env.getId());
}
return username;
}
}