001: /**
002: * $Id: PASMechanism.java,v 1.8 2006/05/22 20:50:08 yue Exp $
003: * Copyright 2004 Sun Microsystems, Inc. All
004: * rights reserved. Use of this product is subject
005: * to license terms. Federal Acquisitions:
006: * Commercial Software -- Government Users
007: * Subject to Standard License Terms and
008: * Conditions.
009: *
010: * Sun, Sun Microsystems, the Sun logo, and Sun ONE
011: * are trademarks or registered trademarks of Sun Microsystems,
012: * Inc. in the United States and other countries.
013: */package com.sun.portal.admin.server;
014:
015: import java.util.NoSuchElementException;
016: import java.util.StringTokenizer;
017: import java.util.logging.Level;
018: import java.util.logging.Logger;
019:
020: import javax.management.Notification;
021: import javax.management.NotificationFilter;
022: import javax.management.NotificationListener;
023: import javax.management.remote.JMXConnectionNotification;
024: import javax.security.auth.Subject;
025: import javax.security.auth.callback.Callback;
026: import javax.security.auth.callback.NameCallback;
027: import javax.security.auth.callback.PasswordCallback;
028:
029: import netscape.ldap.util.DN;
030:
031: import com.iplanet.sso.SSOToken;
032: import com.iplanet.sso.SSOTokenManager;
033: import com.sun.cacao.agent.auth.CacaoCallbackHandler;
034: import com.sun.cacao.agent.auth.CallbackInfo;
035: import com.sun.cacao.agent.auth.Credential;
036: import com.sun.cacao.agent.auth.Mechanism;
037: import com.sun.identity.authentication.AuthContext;
038: import com.sun.identity.authentication.spi.AuthLoginException;
039: import com.sun.portal.admin.common.context.PortalDomainContext;
040: import com.sun.portal.admin.common.context.PortalDomainContextFactory;
041:
042: /**
043: * This class implements an authentication mechanism to authenticate
044: * Portal Admin Server users using Access Manager.
045: */
046: public class PASMechanism implements Mechanism, NotificationFilter,
047: NotificationListener {
048:
049: private static Logger logger = PASLogger.getLogger();
050:
051: /**
052: * Returns the name of this mechanism. It must correspond to the
053: * first field of the SASL/PLAIN authentication-id.
054: *
055: * @return the name of this mechanism.
056: */
057: public String getName() {
058: return AdminServerUtil.JMX_DOMAIN;
059: }
060:
061: /**
062: * Indicates if this mechanism asserts identity.
063: *
064: * @return a <code>boolean</code> value.
065: */
066: public boolean isIdentityAsserted() {
067: return false;
068: }
069:
070: /**
071: * Parses and validates the given credentials, returning a CallbackInfo.
072: *
073: * @return a <code>CallbackInfo</code> value.
074: * @param authenticationID a <code>String</code> value.
075: * @param password a <code>String</code> value.
076: * @param authPieces a <code>String[]</code> value containing fields.
077: * @param passwdPieces a <code>String[]</code> value containing fields.
078: * @exception SecurityException if an error occurs.
079: */
080: public CallbackInfo parse(String authenticationID, String password,
081: String[] authPieces, String[] passwdPieces)
082: throws SecurityException {
083:
084: String message = null;
085: String userName = authPieces[1];
086: String domainID = authPieces[2];
087: String uid = userName;
088: PortalDomainContext pdc = null;
089: String orgDN = null;
090:
091: try {
092: pdc = PortalDomainContextFactory
093: .getPortalDomainContext(domainID);
094: } catch (NoSuchElementException e) {
095: // This portal domain context has not been loaded, so try
096: // to initialize it now.
097: try {
098: PASModule.initPortalDomainContext(domainID);
099: pdc = PortalDomainContextFactory
100: .getPortalDomainContext(domainID);
101: } catch (Exception ex) {
102: message = "Authentication failed: " + ex.getMessage();
103: //logger.log(Level.SEVERE, message, ex);
104: logger.log(Level.SEVERE, "PSAD_CSPAS0101", ex
105: .getMessage());
106: logger.log(Level.SEVERE, "PSAD_CSPAS0000", ex);
107: throw new SecurityException(message);
108: }
109: } catch (Exception e) {
110: message = "Authentication failed: " + e.getMessage();
111: //logger.log(Level.SEVERE, message, e);
112: logger.log(Level.SEVERE, "PSAD_CSPAS0101", e.getMessage());
113: logger.log(Level.SEVERE, "PSAD_CSPAS0000", e);
114: throw new SecurityException(message);
115: }
116:
117: if (DN.isDN(userName)) {
118: // The userName is a DN and it should have the form
119: // uid=<uid>,ou=People,<orgDN>. We need to extract the
120: // uid and the orgDN from it.
121: DN userDN = new DN(userName);
122: String[] components = userDN.explodeDN(true);
123:
124: if (components.length < 3) {
125: message = "Authentication failed due to incorrect user "
126: + "DN format: " + userName;
127:
128: //logger.log(Level.WARNING, message);
129: logger.log(Level.WARNING, "PSAD_CSPAS0102", userName);
130: throw new SecurityException(message);
131: }
132:
133: uid = components[0];
134: orgDN = userDN.getParent().getParent().toString();
135: } else {
136: // The userName is not a DN, so assume it is a uid and we
137: // should authenticate the user against the default org.
138: orgDN = pdc.getDefaultOrg();
139: userName = "uid=" + uid + ",ou=People," + orgDN;
140: }
141:
142: // In this release, only the super user of the portal domain
143: // is allowed to login. Everyone else is denied. In the
144: // future, we need to remove this check in order to allow
145: // login for various types of portal users/admins.
146:
147: DN userDN = new DN(userName);
148: DN super UserDN = new DN(pdc.getSuperUser());
149:
150: if (!userDN.equals(super UserDN)) {
151: message = "Authentication failed: " + userName
152: + " is not a super user";
153: //logger.log(Level.WARNING, message);
154: logger.log(Level.WARNING, "PSAD_CSPAS0103", userName);
155: throw new SecurityException(message);
156: }
157:
158: // FIXME: It isn't clear at all how to ask Access Manager to
159: // authenticate users against different portal domains. The
160: // directory AM uses for authentication is specified in
161: // AMConfig.properties, which is loaded using
162: // ResourceBundle.getBundle(). Maybe we should write a class
163: // to subclass ResourceBundle and have AM load it instead????
164:
165: AuthContext authContext = null;
166:
167: try {
168: authContext = new AuthContext(orgDN);
169: authContext.login(AuthContext.IndexType.MODULE_INSTANCE,
170: "LDAP");
171: } catch (AuthLoginException e) {
172: message = "Authentication failed: " + e.getMessage();
173: //logger.log(Level.WARNING, message, e);
174: logger.log(Level.WARNING, "PSAD_CSPAS0101", e.getMessage());
175: logger.log(Level.WARNING, "PSAD_CSPAS0000", e);
176: throw new SecurityException(message);
177: }
178:
179: while (authContext.hasMoreRequirements()) {
180: Callback[] callbacks = authContext.getRequirements();
181:
182: if (callbacks != null) {
183: for (int i = 0; i < callbacks.length; i++) {
184: if (callbacks[i] instanceof NameCallback) {
185: NameCallback ncb = (NameCallback) callbacks[i];
186: ncb.setName(uid);
187: } else if (callbacks[i] instanceof PasswordCallback) {
188: PasswordCallback pcb = (PasswordCallback) callbacks[i];
189: pcb.setPassword(passwdPieces[0].toCharArray());
190: }
191: }
192:
193: authContext.submitRequirements(callbacks);
194: }
195: }
196:
197: AuthContext.Status loginStatus = authContext.getStatus();
198:
199: if (loginStatus == AuthContext.Status.SUCCESS) {
200: Subject subject = authContext.getSubject();
201: PASPrincipal pasPrincipal = new PASPrincipal(userName,
202: domainID);
203:
204: try {
205: pasPrincipal.setSSOToken(authContext.getSSOToken());
206: } catch (Exception e) {
207: message = "Authentication failed: " + e.getMessage();
208: //logger.log(Level.WARNING, message, e);
209: logger.log(Level.WARNING, "PSAD_CSPAS0101", e
210: .getMessage());
211: logger.log(Level.WARNING, "PSAD_CSPAS0000", e);
212: throw new SecurityException(message);
213: }
214:
215: subject.getPrincipals().add(pasPrincipal);
216: //logger.finer("Authentication succeeded: " + subject);
217: logger.log(Level.FINER, "PSAD_CSPAS0104", subject);
218: subject.getPublicCredentials().add(
219: new Credential(authenticationID));
220: subject.getPrivateCredentials().add(
221: new Credential(password));
222: return new CallbackInfo(subject, false);
223: } else {
224: AuthLoginException e = authContext.getLoginException();
225: message = "Authentication failed: " + e.getMessage();
226: //logger.log(Level.WARNING, message, e);
227: logger.log(Level.WARNING, "PSAD_CSPAS0101", e.getMessage());
228: logger.log(Level.WARNING, "PSAD_CSPAS0000", e);
229: throw new SecurityException(message);
230: }
231: }
232:
233: /**
234: * Invoked before sending the specified notification to the
235: listener. Only JMX connection notification is of interest.
236: *
237: * @param notification The notification to be sent.
238: * @return <code>true</code> if the notification is a JMX
239: * connection notification.
240: */
241: public boolean isNotificationEnabled(Notification notification) {
242: return notification instanceof JMXConnectionNotification;
243: }
244:
245: /**
246: * Invoked when a JMX notification occurs. If it is a
247: * connection-closed or connection-failed notification, the
248: * SSOToken is destroyed.
249: *
250: * @param notification The notification.
251: * @param handback An opaque object which helps the listener to
252: * associate information regarding the MBean emitter
253: */
254: public void handleNotification(Notification notification,
255: Object handback) {
256: String type = notification.getType();
257:
258: if (JMXConnectionNotification.CLOSED.equals(type)
259: || JMXConnectionNotification.FAILED.equals(type)) {
260:
261: //logger.finer("Received notification of type " + type);
262: logger.log(Level.FINER, "PSAD_CSPAS0105", type);
263:
264: JMXConnectionNotification connectionNotification = (JMXConnectionNotification) notification;
265:
266: String connectionID = connectionNotification
267: .getConnectionId();
268: //logger.finer("Connection ID: " + connectionID);
269: logger.log(Level.FINER, "PSAD_CSPAS0106", connectionID);
270: StringTokenizer st = new StringTokenizer(connectionID, " ");
271:
272: if (st.countTokens() > 2) {
273: String protocol = st.nextToken();
274:
275: StringTokenizer principals = new StringTokenizer(st
276: .nextToken(), ";");
277:
278: // One of these principals is the SSOTokenPrincipal.
279: while (principals.hasMoreTokens()) {
280: String principal = principals.nextToken();
281:
282: if (principal.equals("root")) {
283: continue;
284: }
285:
286: if (principal.startsWith("com")) {
287: continue;
288: }
289:
290: if (DN.isDN(principal)) {
291: continue;
292: }
293:
294: try {
295: SSOTokenManager manager = SSOTokenManager
296: .getInstance();
297: SSOToken token = manager
298: .createSSOToken(principal);
299: manager.destroyToken(token);
300: //logger.finer("SSOToken destroyed: " + principal);
301: logger.log(Level.FINER, "PSAD_CSPAS0107",
302: principal);
303: } catch (Exception e) {
304: }
305: }
306: }
307: }
308: }
309: }
|