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