001: /* Copyright 2001 The JA-SIG Collaborative. All rights reserved.
002: * See license distributed with this file and
003: * available online at http://www.uportal.org/license.html
004: */
005:
006: package org.jasig.portal.services;
007:
008: import java.util.Enumeration;
009: import java.util.HashMap;
010: import java.util.Iterator;
011: import java.util.Map;
012:
013: import org.apache.commons.logging.Log;
014: import org.apache.commons.logging.LogFactory;
015: import org.jasig.portal.AuthorizationException;
016: import org.jasig.portal.UserIdentityStoreFactory;
017: import org.jasig.portal.events.EventPublisherLocator;
018: import org.jasig.portal.events.support.UserLoggedInPortalEvent;
019: import org.jasig.portal.properties.PropertiesManager;
020: import org.jasig.portal.security.IAdditionalDescriptor;
021: import org.jasig.portal.security.IOpaqueCredentials;
022: import org.jasig.portal.security.IPerson;
023: import org.jasig.portal.security.IPrincipal;
024: import org.jasig.portal.security.ISecurityContext;
025: import org.jasig.portal.security.PortalSecurityException;
026: import org.jasig.portal.security.provider.ChainingSecurityContext;
027: import org.jasig.portal.services.persondir.IPersonAttributeDao;
028: import org.jasig.portal.utils.MovingAverage;
029: import org.jasig.portal.utils.MovingAverageSample;
030:
031: /**
032: * Attempts to authenticate a user and retrieve attributes
033: * associated with the user.
034: * @author Ken Weiner, kweiner@unicon.net
035: * @author Don Fracapane (df7@columbia.edu)
036: * Added properties in the security properties file that hold the tokens used to
037: * represent the principal and credential for each security context. This version
038: * differs in the way the principal and credentials are set (all contexts are set
039: * up front after evaluating the tokens). See setContextParameters() also.
040: * @version $Revision: 36735 $
041: * Changes put in to allow credentials and principals to be defined and held by each
042: * context.
043: */
044: public class Authentication {
045:
046: private static final Log log = LogFactory
047: .getLog(Authentication.class);
048:
049: private final static String BASE_CONTEXT_NAME = "root";
050:
051: protected org.jasig.portal.security.IPerson m_Person = null;
052: protected ISecurityContext ic = null;
053:
054: // Metric counters
055: private static final MovingAverage authenticationTimes = new MovingAverage();
056: public static MovingAverageSample lastAuthentication = new MovingAverageSample();
057:
058: /**
059: * Attempts to authenticate a given IPerson based on a set of principals and credentials
060: * @param principals
061: * @param credentials
062: * @param person
063: * @exception PortalSecurityException
064: */
065: public void authenticate(HashMap principals, HashMap credentials,
066: IPerson person) throws PortalSecurityException {
067:
068: // Retrieve the security context for the user
069: ISecurityContext securityContext = person.getSecurityContext();
070:
071: //Set the principals and credentials for the security context chain
072: this .configureSecurityContextChain(principals, credentials,
073: person, securityContext, BASE_CONTEXT_NAME);
074:
075: // NOTE: The LoginServlet looks in the security.properties file to
076: // determine what tokens to look for that represent the principals and
077: // credentials for each context. It then retrieves the values from the request
078: // and stores the values in the principals and credentials HashMaps that are
079: // passed to the Authentication service.
080:
081: // Attempt to authenticate the user
082: final long start = System.currentTimeMillis();
083: securityContext.authenticate();
084: final long elapsed = System.currentTimeMillis() - start;
085: // Check to see if the user was authenticated
086: if (securityContext.isAuthenticated()) {
087: lastAuthentication = authenticationTimes.add(elapsed); // metric
088: // Add the authenticated username to the person object
089: // the login name may have been provided or reset by the security provider
090: // so this needs to be done after authentication.
091: person.setAttribute(IPerson.USERNAME, securityContext
092: .getPrincipal().getUID());
093: // Retrieve the additional descriptor from the security context
094: IAdditionalDescriptor addInfo = person.getSecurityContext()
095: .getAdditionalDescriptor();
096: // Process the additional descriptor if one was created
097: if (addInfo != null) {
098: // Replace the passed in IPerson with the additional descriptor if the
099: // additional descriptor is an IPerson object created by the security context
100: // NOTE: This is not the preferred method, creation of IPerson objects should be
101: // handled by the PersonManager.
102: if (addInfo instanceof IPerson) {
103: IPerson newPerson = (IPerson) addInfo;
104: person.setFullName(newPerson.getFullName());
105: for (Enumeration e = newPerson.getAttributeNames(); e
106: .hasMoreElements();) {
107: String attributeName = (String) e.nextElement();
108: person.setAttribute(attributeName, newPerson
109: .getAttribute(attributeName));
110: }
111: resetEntityIdentifier(person, newPerson);
112: }
113: // If the additional descriptor is a map then we can
114: // simply copy all of these additional attributes into the IPerson
115: else if (addInfo instanceof Map) {
116: // Cast the additional descriptor as a Map
117: Map additionalAttributes = (Map) addInfo;
118: // Copy each additional attribute into the person object
119: for (Iterator keys = additionalAttributes.keySet()
120: .iterator(); keys.hasNext();) {
121: // Get a key
122: String key = (String) keys.next();
123: // Set the attribute
124: person.setAttribute(key, additionalAttributes
125: .get(key));
126: }
127: } else if (addInfo instanceof ChainingSecurityContext.ChainingAdditionalDescriptor) {
128: // do nothing
129: } else {
130: if (log.isWarnEnabled())
131: log.warn("Authentication Service recieved "
132: + "unknown additional descriptor ["
133: + addInfo + "]");
134: }
135: }
136: // Populate the person object using the PersonDirectory if applicable
137: if (PropertiesManager
138: .getPropertyAsBoolean("org.jasig.portal.services.Authentication.usePersonDirectory")) {
139: // Retrieve all of the attributes associated with the person logging in
140: IPersonAttributeDao pa = PersonDirectory
141: .getPersonAttributeDao();
142: Map attribs = pa.getUserAttributes(getUsername(person));
143:
144: if (attribs != null) {
145: // attribs may be null. IPersonAttributeDao returns null when it does not recognize a user at all, as
146: // distinguished from returning an empty Map of attributes when it recognizes a user has having no
147: // attributes.
148:
149: // Add each of the attributes to the IPerson
150: Iterator en = attribs.keySet().iterator();
151: while (en.hasNext()) {
152: String key = (String) en.next();
153: // String value = (String)attribs.get(key);
154: // person.setAttribute(key, value);
155: person.setAttribute(key, attribs.get(key));
156: }
157: }
158:
159: }
160: // Make sure the the user's fullname is set
161: if (person.getFullName() == null) {
162: // Use portal display name if one exists
163: if (person.getAttribute("portalDisplayName") != null) {
164: person.setFullName((String) person
165: .getAttribute("portalDisplayName"));
166: }
167: // If not try the eduPerson displyName
168: else if (person.getAttribute("displayName") != null) {
169: person.setFullName((String) person
170: .getAttribute("displayName"));
171: }
172: // If still no FullName use an unrecognized string
173: if (person.getFullName() == null) {
174: person.setFullName("Unrecognized person: "
175: + person.getAttribute(IPerson.USERNAME));
176: }
177: }
178: // Find the uPortal userid for this user or flunk authentication if not found
179: // The template username should actually be derived from directory information.
180: // The reference implemenatation sets the uPortalTemplateUserName to the default in
181: // the portal.properties file.
182: // A more likely template would be staff or faculty or undergraduate.
183: boolean autocreate = PropertiesManager
184: .getPropertyAsBoolean("org.jasig.portal.services.Authentication.autoCreateUsers");
185: // If we are going to be auto creating accounts then we must find the default template to use
186: if (autocreate
187: && person.getAttribute("uPortalTemplateUserName") == null) {
188: String defaultTemplateUserName = PropertiesManager
189: .getProperty("org.jasig.portal.services.Authentication.defaultTemplateUserName");
190: person.setAttribute("uPortalTemplateUserName",
191: defaultTemplateUserName);
192: }
193: try {
194: // Attempt to retrieve the UID
195: int newUID = UserIdentityStoreFactory
196: .getUserIdentityStoreImpl().getPortalUID(
197: person, autocreate);
198: person.setID(newUID);
199: } catch (AuthorizationException ae) {
200: log.error("Exception retrieving ID", ae);
201: throw new PortalSecurityException(
202: "Authentication Service: Exception retrieving UID");
203: }
204:
205: //TODO add IPerson cache
206:
207: // Record the successful authentication
208: EventPublisherLocator.getApplicationEventPublisher()
209: .publishEvent(
210: new UserLoggedInPortalEvent(this , person));
211: }
212: }
213:
214: /**
215: * Return the username to be used for authorization (exit hook)
216: * @param person
217: * @return usernmae
218: */
219: protected String getUsername(final IPerson person) {
220: return (String) person.getAttribute(IPerson.USERNAME);
221: }
222:
223: /**
224: * Reset the entity identifier in the final person object (exit hook)
225: * @param person
226: * @param newPerson
227: */
228: protected void resetEntityIdentifier(final IPerson person,
229: final IPerson newPerson) {
230: }
231:
232: /**
233: * Returns an IPerson object that can be used to hold site-specific attributes
234: * about the logged on user. This information is established during
235: * authentication.
236: * @return An object that implements the
237: * <code>org.jasig.portal.security.IPerson</code> interface.
238: */
239: public IPerson getPerson() {
240: return m_Person;
241: }
242:
243: /**
244: * Returns an ISecurityContext object that can be used
245: * later. This object is passed as part of the IChannel Interface.
246: * The security context may be used to gain authorized access to
247: * services.
248: * @return An object that implements the
249: * <code>org.jasig.portal.security.ISecurityContext</code> interface.
250: */
251: public ISecurityContext getSecurityContext() {
252: return ic;
253: }
254:
255: /**
256: * Get the principal and credential for a specific context and store them in
257: * the context.
258: * @param principals
259: * @param credentials
260: * @param ctxName
261: * @param securityContext
262: * @param person
263: */
264: public void setContextParameters(HashMap principals,
265: HashMap credentials, String ctxName,
266: ISecurityContext securityContext, IPerson person) {
267: String username = (String) principals.get(ctxName);
268: String credential = (String) credentials.get(ctxName);
269: // If username or credential are null, this indicates that the token was not
270: // set in security properties. We will then use the value for root.
271: username = (username != null ? username : (String) principals
272: .get(BASE_CONTEXT_NAME));
273: credential = (credential != null ? credential
274: : (String) credentials.get(BASE_CONTEXT_NAME));
275: if (log.isDebugEnabled())
276: log
277: .debug("Authentication::setContextParameters() username: "
278: + username);
279: // Retrieve and populate an instance of the principal object
280: IPrincipal principalInstance = securityContext
281: .getPrincipalInstance();
282: if (username != null && !username.equals("")) {
283: principalInstance.setUID(username);
284: }
285: // Retrieve and populate an instance of the credentials object
286: IOpaqueCredentials credentialsInstance = securityContext
287: .getOpaqueCredentialsInstance();
288: credentialsInstance.setCredentials(credential);
289: }
290:
291: /**
292: * Recureses through the {@link ISecurityContext} chain, setting the credentials
293: * for each.
294: * TODO This functionality should be moved into the {@link org.jasig.portal.security.provider.ChainingSecurityContext}.
295: *
296: * @param principals
297: * @param credentials
298: * @param person
299: * @param securityContext
300: * @param baseContextName
301: * @throws PortalSecurityException
302: */
303: private void configureSecurityContextChain(
304: final HashMap principals, final HashMap credentials,
305: final IPerson person,
306: final ISecurityContext securityContext,
307: final String baseContextName)
308: throws PortalSecurityException {
309: this .setContextParameters(principals, credentials,
310: baseContextName, securityContext, person);
311:
312: // load principals and credentials for the subContexts
313: for (final Enumeration subCtxNames = securityContext
314: .getSubContextNames(); subCtxNames.hasMoreElements();) {
315: final String fullSubCtxName = (String) subCtxNames
316: .nextElement();
317:
318: //Strip off the base of the name
319: String localSubCtxName = fullSubCtxName;
320: if (fullSubCtxName.startsWith(baseContextName + ".")) {
321: localSubCtxName = localSubCtxName
322: .substring(baseContextName.length() + 1);
323: }
324:
325: final ISecurityContext sc = securityContext
326: .getSubContext(localSubCtxName);
327:
328: this.configureSecurityContextChain(principals, credentials,
329: person, sc, fullSubCtxName);
330: }
331: }
332: }
|