001/**
002 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003 *
004 * Copyright (c) 2005 Sun Microsystems Inc. All Rights Reserved
005 *
006 * The contents of this file are subject to the terms
007 * of the Common Development and Distribution License
008 * (the License). You may not use this file except in
009 * compliance with the License.
010 *
011 * You can obtain a copy of the License at
012 * https://opensso.dev.java.net/public/CDDLv1.0.html or
013 * opensso/legal/CDDLv1.0.txt
014 * See the License for the specific language governing
015 * permission and limitations under the License.
016 *
017 * When distributing Covered Code, include this CDDL
018 * Header Notice in each file and include the License file
019 * at opensso/legal/CDDLv1.0.txt.
020 * If applicable, add the following below the CDDL Header,
021 * with the fields enclosed by brackets [] replaced by
022 * your own identifying information:
023 * "Portions Copyrighted [year] [name of copyright owner]"
024 *
025 * $Id: SSOTokenManager.java,v 1.7 2009/02/18 23:59:36 qcheng Exp $
026 *
027 * Portions copyright 2014-2015 ForgeRock AS.
028 */
029
030package com.iplanet.sso;
031
032import java.security.Principal;
033import java.util.Set;
034
035import javax.servlet.http.HttpServletRequest;
036
037import org.forgerock.guice.core.InjectorHolder;
038import org.forgerock.openam.sso.providers.stateless.StatelessSSOProvider;
039import org.forgerock.openam.sso.providers.stateless.StatelessSessionManager;
040import org.forgerock.openam.utils.StringUtils;
041
042import com.iplanet.am.util.SystemProperties;
043import com.iplanet.dpro.session.SessionException;
044import com.iplanet.dpro.session.SessionID;
045import com.iplanet.services.util.I18n;
046import com.iplanet.sso.providers.dpro.SSOProviderBundle;
047import com.iplanet.ums.IUMSConstants;
048import com.sun.identity.shared.debug.Debug;
049
050/**
051 * SSOTokenManager is the final class that is the mediator between the SSO APIs
052 * and SSO providers. When an SSO client makes an API invocation,
053 * SSOTokenManager will delegate that call to the SSO provider/plug-in. The SSO
054 * provider will execute the call and return the results to SSOTokenManager,
055 * which in turn returns the results to the SSO client. This decouples the SSO
056 * clients from the actual SSO providers. You should be able to replace the SSO
057 * provider without having to modify the SSO client. However, the clients can
058 * invoke the class methods on the objects returned by the SSOTokenManager.
059 * <p>
060 * SSOTokenManager is a singleton class; there can be, at most, only one
061 * instance of SSOTokenManager in any given JVM. <p> SSOTokenManager currently
062 * supports only two kinds of provider: Grappa and OpenAM.
063 * <p> It is assumed that the provider classes or the JAR file is in the
064 * CLASSPATH so that they can be found automatically. Providers can be
065 * configured using <code>providerimplclass</code> property.
066 * This property must be set to the complete (absolute) package name of the
067 * main class of the provider. For example, if the provider class is
068 * com.iplanet.sso.providers.dpro.SSOProviderImpl, that entire class name
069 * including package prefixes MUST be specified. The main class MUST implement
070 * the com.iplanet.sso.SSOProvider interface and MUST have a public no-arg
071 * default constructor.
072 * <p>
073 * The class <code>SSOTokenManager</code> is a <code>final</code> class that
074 * provides interfaces to create and validate <code>SSOToken</code>s.
075 * <p>
076 * It is a singleton class; an instance of this class can be obtained by calling
077 * <code>SSOTokenManager.getInstance()</code>.
078 * <p>
079 * Having obtained an instance of <code>SSOTokenManager</code>, its methods
080 * can be called to create <code>SSOToken</code>, get <code>SSOToken</code>
081 * given the <code>SSOTokenID</code> in string format, and to validate
082 * <code>SSOToken</code>s.
083 *
084 * @supported.api
085 */
086public class SSOTokenManager {
087
088    /*
089     * SSOTokenManager is not a real PROVIDER but implements SSOProvider for
090     * consistency in the methods.
091     */
092
093    /**
094     * Grappa SSOProvider class that will be used by default if
095     * providerimplclass property is not present.
096     */
097    static final String GRAPPA_PROVIDER_PACKAGE =
098        "com.sun.identity.authentication.internal";
099
100    /**
101     * Package name of the stateless SSO provider.
102     */
103    private static final String STATELESS_PROVIDER_PACKAGE = StatelessSSOProvider.class.getPackage().getName();
104
105    static SSOProvider grappaProvider = null;
106
107    /**
108     * DPRO SSOProvider class
109     */
110    static SSOProvider dProProvider = null;
111
112    /** Debug class that can be used by SSOProvider implementations */
113    public static Debug debug = Debug.getInstance(IUMSConstants.UMS_DEBUG);
114
115    // Singleton instance of SSOTokenManager
116    private static volatile SSOTokenManager instance = null;
117
118    // Guice provided, lazy initialised, Server Mode only, for Stateless Sessions.
119    private static SSOProvider statelessProvider;
120    private static StatelessSessionManager statelessSessionManager;
121
122    /**
123     * Returns the singleton instance of
124     * <code>SSOTokenManager</code>.
125     *
126     * @return The singleton <code>SSOTokenManager</code> instance
127     * @throws SSOException
128     *             if unable to get the singleton <code>SSOTokenManager</code>
129     *             instance.
130     * @supported.api
131     */
132    public static SSOTokenManager getInstance() throws SSOException {
133
134        /*
135         * We will use the double-checked locking pattern. Rarely entered block.
136         * Push synchronization inside it. This is the first check.
137         */
138
139        if (instance == null) {
140            /*
141             * Only 1 thread at a time gets past the next point. Rarely executed
142             * synchronization statement and hence synchronization penalty is
143             * not paid every time this method is called.
144             */
145
146            synchronized (SSOTokenManager.class) {
147                /*
148                 * If a second thread was waiting to get here, it will now find
149                 * that the instance has already been initialized, and it will
150                 * not re-initialize the instance variable. This is the
151                 * double-check.
152                 */
153
154                if (instance == null) {
155                    /*
156                     * Here is the critical section that lazy initializes the
157                     * singleton variable.
158                     */
159                    debug.message(
160                            "Constructing a new instance of SSOTokenManager");
161                    instance = new SSOTokenManager();
162                }
163            }
164        }
165        return (instance);
166    }
167
168    /**
169     * Note: SSOTokenManager is initialised early in system sequence. This lazy initialiser is required
170     * to smooth over this issue.
171     * @return Null if client mode, otherwise non null Guice initialised.
172     */
173    private SSOProvider getStatelessProvider() {
174        if (!SystemProperties.isServerMode()) {
175            return null;
176        }
177
178        if (statelessProvider == null) {
179            statelessProvider = InjectorHolder.getInstance(StatelessSSOProvider.class);
180        }
181        return statelessProvider;
182    }
183
184    /**
185     * Note: SSOTokenManager is initialised early in system sequence. This lazy initialiser is required
186     * to smooth over this issue.
187     * @return Null if client mode, otherwise non null Guice initialised.
188     */
189    private StatelessSessionManager getStatelessSessionManager() {
190        if (!SystemProperties.isServerMode()) {
191            return null;
192        }
193        if (statelessSessionManager == null) {
194            statelessSessionManager = InjectorHolder.getInstance(StatelessSessionManager.class);
195        }
196        return statelessSessionManager;
197    }
198
199    /**
200     * Since this class is a singleton, the constructor is suppressed. This
201     * constructor will try to find the PROVIDER jar files, load them, then find
202     * the PROVIDER mainclass, instantiate it and store it in PROVIDER.
203     * Providers can be configured using <code>providerimplclass</code> Java
204     * property. This property must be set to the complete (absolute) package
205     * name of the main class of the PROVIDER. The main class MUST implement the
206     * com.iplanet.sso.SSOProvider interface and MUST have a public no-arg
207     * default constructor.
208     */
209    private SSOTokenManager() throws SSOException {
210        Throwable dProException = null;
211        // Obtain the Grappa PROVIDER class
212        try {
213            grappaProvider = new
214                com.sun.identity.authentication.internal.AuthSSOProvider();
215            if (debug.messageEnabled()) {
216                debug.message("Obtained Grappa SSO Provider");
217            }
218        } catch (Throwable e) {
219            debug.error("Unable to obtain Grappa SSO PROVIDER", e);
220            dProException = e;
221        }
222
223        // Obtain the DPRO provide class
224        try {
225            dProProvider = new com.iplanet.sso.providers.dpro.SSOProviderImpl();
226            if (debug.messageEnabled()) {
227                debug.message("Obtained DPRO SSO Provider");
228            }
229        } catch (Throwable e) {
230            debug.error("DPRO SSO Provider Exception", e);
231            dProException = e;
232        }
233
234        if (dProProvider == null && grappaProvider == null) {
235            debug.error("Unable to obtain either GRAPPA or DPRO SSO providers");
236            I18n i18n = I18n.getInstance(IUMSConstants.UMS_PKG);
237            String rbName = i18n.getResBundleName();
238            if (dProException instanceof ClassNotFoundException)
239                throw new SSOException(rbName,
240                        IUMSConstants.SSO_NOPROVIDERCLASS, null);
241            else if (dProException instanceof InstantiationException)
242                throw new SSOException(rbName,
243                        IUMSConstants.SSO_NOPROVIDERINSTANCE, null);
244            else if (dProException instanceof IllegalAccessException)
245                throw new SSOException(rbName, IUMSConstants.SSO_ILLEGALACCESS,
246                        null);
247            else
248                throw new SSOException(dProException);
249        }
250    }
251
252    /**
253     * Get PROVIDER based on SSOToken provided
254     * @param token Single signon SSOToken
255     * @exception SSOException in case of erros when getting the PROVIDER
256     */
257    private SSOProvider getProvider(SSOToken token) throws SSOException {
258        if (token == null) {
259            throw new SSOException(SSOProviderBundle.rbName, "ssotokennull", null);
260        }
261
262        String packageName = token.getClass().getName();
263        if (packageName.startsWith(STATELESS_PROVIDER_PACKAGE)) {
264            return getStatelessProvider();
265        } else if (packageName.startsWith(GRAPPA_PROVIDER_PACKAGE)) {
266            if (grappaProvider == null) {
267                I18n i18n = I18n.getInstance(IUMSConstants.UMS_PKG);
268                throw new SSOException(i18n.getResBundleName(),
269                        IUMSConstants.SSO_NOPROVIDERCLASS, null);
270            }
271            return (grappaProvider);
272        }
273        return (dProProvider);
274    }
275
276    /**
277     * Creates a single sign on token from <code>HttpServletRequest</code>
278     *
279     * @param request
280     *            The <code>HttpServletRequest</code> object which contains
281     *            the session string.
282     * @return single sign on <code>SSOToken</code>
283     * @exception SSOException
284     *                if the single sign on token cannot be created.
285     * @exception UnsupportedOperationException
286     *                if this is an unsupported operation.
287     * @supported.api
288     */
289    public SSOToken createSSOToken(
290            javax.servlet.http.HttpServletRequest request)
291            throws UnsupportedOperationException, SSOException {
292
293        if (containsJwt(request)) {
294            return getStatelessProvider().createSSOToken(request);
295        }
296
297        if (dProProvider != null)
298            return (dProProvider.createSSOToken(request));
299        else
300            return (grappaProvider.createSSOToken(request));
301    }
302
303    /**
304     * Creates a single sign on token after authenticating
305     * the principal with the given password. This method of creating a single
306     * sign on token should only be used for command line applications and it is
307     * forbidden to use this single sign on token in any other context (e.g.
308     * policy, federation, etc.). A token created with this method is only valid
309     * within the context of the calling application. Once the process exits the
310     * token will be destroyed. If token is created using this constructor then
311     * ONLY these methods of single sign on token is supported -
312     *
313     * <pre>
314     *  getAuthType(),
315     *  getHostName(),
316     *  getIPAddress(),
317     *  setProperty(String name, String value),
318     *  getProperty(String name),
319     *  isValid(),
320     *  validate().
321     * </pre>
322     *
323     * @param user
324     *            Principal representing a user or service
325     * @param password
326     *            The password supplied for the principal
327     * @return single sign on token
328     * @exception SSOException
329     *                if the single sign on token cannot be created.
330     * @exception UnsupportedOperationException
331     *                if this is an unsupported operation.
332     * @deprecated This method has been deprecated. Please use the regular LDAP
333     *             authentication mechanism instead. More information on how to
334     *             use the authentication programming interfaces as well as the
335     *             code samples can be obtained from the "Authenticating Using
336     *             OpenAM Java SDK" chapter of the OpenAM Developer's Guide.
337     */
338    public SSOToken createSSOToken(Principal user, String password)
339            throws UnsupportedOperationException, SSOException {
340        if (dProProvider != null)
341            return (dProProvider.createSSOToken(user, password));
342        else
343            return (grappaProvider.createSSOToken(user, password));
344    }
345
346    /**
347     * Creates a single sign on token from the single sign
348     * on token ID. Note:-If you want to do Client's IP address validation for
349     * the single sign on token then use
350     * <code>creatSSOToken(String, String)</code> OR
351     * <code>createSSOToken(HttpServletRequest)</code>.
352     *
353     * @param tokenId
354     *            Token ID of the single sign on token
355     * @return single sign on token
356     * @exception SSOException
357     *                if the single sign on token cannot be created.
358     * @exception UnsupportedOperationException
359     * @supported.api
360     */
361    public SSOToken createSSOToken(String tokenId)
362            throws UnsupportedOperationException, SSOException {
363
364        if (StringUtils.isNotBlank(tokenId) && containsJwt(tokenId)) {
365            return getStatelessProvider().createSSOToken(tokenId);
366        }
367
368        if (dProProvider != null)
369            return (dProProvider.createSSOToken(tokenId));
370        else
371            return (grappaProvider.createSSOToken(tokenId));
372    }
373
374    /**
375     * Creates a single sign on token from the single sign
376     * on token ID.
377     *
378     * @param tokenId
379     *            Token ID of the single sign on token
380     * @param clientIP
381     *            Client IP address. This must be the IP address of the
382     *            client/user who is accessing the application.
383     * @return single sign on token
384     * @exception SSOException
385     *                if the single sign on token cannot be created.
386     * @exception UnsupportedOperationException
387     * @supported.api
388     */
389    public SSOToken createSSOToken(String tokenId, String clientIP)
390            throws UnsupportedOperationException, SSOException {
391
392        if (containsJwt(tokenId)) {
393            return getStatelessProvider().createSSOToken(tokenId, clientIP);
394        }
395
396        if (dProProvider != null)
397            return (dProProvider.createSSOToken(tokenId, clientIP));
398        else
399            return (grappaProvider.createSSOToken(tokenId, clientIP));
400    }
401
402    /**
403     * Call this function if you want to retrieve a token whose id you know, you expect to be valid
404     * (this function will not create a new token for you) and you don't want its idle time accidentally
405     * reset.
406     *
407     * @param tokenId The token id of the token you suspect is valid.
408     * @return The valid token, or null if the token id turned out to be rubbish.
409     */
410    public SSOToken retrieveValidTokenWithoutResettingIdleTime(String tokenId)
411            throws UnsupportedOperationException, SSOException {
412
413        if (containsJwt(tokenId)) {
414            return getStatelessProvider().createSSOToken(tokenId, false, false);
415        }
416
417        if (dProProvider != null)
418            return (dProProvider.createSSOToken(tokenId, false, false));
419        else
420            return (grappaProvider.createSSOToken(tokenId, false, false));
421    }
422
423    /**
424     * Returns true if a single sign on token is valid.  Your token may have its idle time reset.
425     * You have been warned.
426     *
427     * @param token
428     *            The single sign on token object to be validated.
429     * @return true if the single sign on token is valid.
430     * @supported.api
431     */
432    public boolean isValidToken(SSOToken token) {
433        return isValidToken(token, true);
434    }
435
436    /**
437     * Returns true if a single sign on token is valid, resetting the token's idle time
438     * if and only if the flag allows us to.
439     *
440     * @param token The single sign on token object to be validated.
441     *
442     * @return true if the single sign on token is valid.
443     * @supported.api
444     * @since 12.0.0
445     */
446    public boolean isValidToken(SSOToken token, boolean resetIdleTime) {
447        try {
448            return (getProvider(token).isValidToken(token, resetIdleTime));
449        } catch (SSOException ignored) {
450            return (false);
451        }
452    }
453
454
455    /**
456     * Returns true if the single sign on token is valid.
457     *
458     * @param token
459     *            The single sign on token object to be validated.
460     * @exception SSOException
461     *                if the single sign on token is not valid.
462     * @supported.api
463     */
464    public void validateToken(SSOToken token) throws SSOException {
465        getProvider(token).validateToken(token);
466    }
467
468    /**
469     * Destroys a single sign on token.
470     *
471     * @param token
472     *            The single sign on token object to be destroyed.
473     * @exception SSOException
474     *                if there was an error while destroying the token, or the
475     *                corresponding session reached its maximum session/idle
476     *                time, or the session was destroyed.
477     * @supported.api
478     */
479    public void destroyToken(SSOToken token) throws SSOException {
480        getProvider(token).destroyToken(token);
481    }
482
483    /**
484     * Refresh the Session corresponding to the single
485     * sign on token from the Session Server. This method should only be used
486     * when the client cannot wait the "session cache interval" for updates on
487     * any changes made to the session properties in the session server. If the
488     * client is remote, calling this method results in an over the wire request
489     * to the session server.
490     *
491     * @param token
492     *            single sign on token
493     * @exception SSOException
494     *                if the session reached its maximum session time, or the
495     *                session was destroyed, or there was an error while
496     *                refreshing the session.
497     * @supported.api
498     */
499    public void refreshSession(SSOToken token) throws SSOException {
500        try {
501            getProvider(token).refreshSession(token);
502        } catch (Exception e) {
503            debug.error("Error in refreshing the session from session server");
504            throw new SSOException(e);
505        }
506    }
507
508    /**
509     * This function will never reset the idle time of the refreshed token.  Otherwise, see
510     * {@link com.iplanet.sso.SSOTokenManager#refreshSession(SSOToken)}
511     *
512     * @param token single sign on token
513     * @exception SSOException
514     *                if the session reached its maximum session time, or the
515     *                session was destroyed, or there was an error while
516     *                refreshing the session.
517     * @since 12.0.0
518     */
519    public void refreshSessionWithoutIdleReset(SSOToken token) throws SSOException {
520        try {
521            getProvider(token).refreshSession(token, false);
522        } catch (Exception e) {
523            debug.error("Error in refreshing the session from session server");
524            throw new SSOException(e);
525        }
526    }
527
528    /**
529     * Destroys a single sign on token.
530     *
531     * @param destroyer
532     *            The single sign on token object used to authorize the
533     *            operation
534     * @param destroyed
535     *            The single sign on token object to be destroyed.
536     * @throws SSOException
537     *             if the there was an error during communication with session
538     *             service.
539     * @supported.api
540     */
541    public void destroyToken(SSOToken destroyer, SSOToken destroyed)
542            throws SSOException {
543        getProvider(destroyer).destroyToken(destroyer, destroyed);
544    }
545
546    /**
547     * Returns a list of single sign on token objects
548     * which correspond to valid Sessions accessible to requester. Single sign
549     * on tokens returned are restricted: they can only be used to retrieve
550     * properties and destroy sessions they represent.
551     *
552     * @param requester
553     *            The single sign on token object used to authorize the
554     *            operation
555     * @param server
556     *            The server for which the valid sessions are to be retrieved
557     * @return Set The set of single sign on tokens representing valid Sessions.
558     * @throws SSOException
559     *             if the there was an error during communication with session
560     *             service.
561     * @supported.api
562     */
563    public Set getValidSessions(SSOToken requester, String server)
564            throws SSOException {
565        return getProvider(requester).getValidSessions(requester, server);
566    }
567
568    /**
569     * Logs out of any OpenAM session associated with the token without destroying the token itself.
570     *
571     * @param token the token to log out.
572     * @throws SSOException if an error occurs.
573     * @since 13.0.0
574     */
575    public void logout(SSOToken token) throws SSOException {
576        getProvider(token).logout(token);
577    }
578
579    /**
580     * Helper function to ensure that JWT based checks are only applied in server mode.
581     * @param object Non null HttpServletRequest, SessionID or TokenID in String format.
582     * @return True if the object contained a JWT.
583     */
584    private boolean containsJwt(Object object) {
585        if (!SystemProperties.isServerMode()) return false;
586
587        try {
588            if (object instanceof HttpServletRequest) {
589                return getStatelessSessionManager().containsJwt((HttpServletRequest) object);
590            } else if (object instanceof SessionID) {
591                return getStatelessSessionManager().containsJwt((SessionID) object);
592            }
593            return getStatelessSessionManager().containsJwt(object.toString());
594        } catch (SessionException | IllegalArgumentException e) {
595            debug.message("Error whilst inspecting JWT:\nClass: {0}\n{1}",
596                    object.getClass(), object, e);
597            return false;
598        }
599    }
600}