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: SSOProviderImpl.java,v 1.9 2009/02/19 05:04:01 bhavnab Exp $
026 *
027 */
028
029/**
030 * Portions copyright 2013-2015 ForgeRock AS.
031 */
032package com.iplanet.sso.providers.dpro;
033
034import com.iplanet.am.util.SystemProperties;
035import com.iplanet.dpro.session.Session;
036import com.iplanet.dpro.session.SessionException;
037import com.iplanet.dpro.session.SessionID;
038import com.iplanet.sso.SSOException;
039import com.iplanet.sso.SSOProvider;
040import com.iplanet.sso.SSOToken;
041import com.iplanet.sso.SSOTokenID;
042import com.sun.identity.common.SearchResults;
043import com.sun.identity.shared.debug.Debug;
044import org.forgerock.openam.session.SessionCache;
045import org.forgerock.openam.utils.ClientUtils;
046import org.forgerock.util.annotations.VisibleForTesting;
047
048import javax.servlet.http.HttpServletRequest;
049import java.net.InetAddress;
050import java.util.HashSet;
051import java.util.Iterator;
052import java.util.Set;
053
054/**
055 * This <code>final</code> class <code>SSOProviderImpl</code> implements
056 * <code>SSOProvider</code> interface and provides implementation of the methods
057 * to create , destroy , check the validity of a single sign on token.
058 *
059 * @supported.api
060 *
061 * Note: Used by ClientSDK, therefore must not use Guice for initialisation.
062 */
063public final class SSOProviderImpl implements SSOProvider {
064
065    /**
066     * Debug SSOProvider
067     */
068    public static Debug debug = null;
069
070    /**
071     * Check to see if the clientIPCheck is enabled
072     */
073    private static boolean checkIP = Boolean.valueOf(
074            SystemProperties.get("com.iplanet.am.clientIPCheckEnabled"))
075            .booleanValue();
076
077    private final SessionCache sessionCache;
078
079    // Initialize debug instance;
080    static {
081        debug = Debug.getInstance("amSSOProvider");
082    }
083
084    /**
085     * Constructs a instance of <code>SSOProviderImpl</code>
086     *
087     * @throws SSOException
088     * @supported.api
089     */
090    public SSOProviderImpl() throws SSOException {
091        this(SessionCache.getInstance());
092    }
093
094    @VisibleForTesting
095    SSOProviderImpl(SessionCache sessionCache) {
096        this.sessionCache = sessionCache;
097    }
098
099    /**
100     * Creates a single sign on token for the <code>HttpRequest</code>
101     *
102     * @param request <code>HttpServletRequest</code>
103     * @return single sign on token for the request
104     * @throws SSOException if the single sign on token cannot be created.
105     */
106    public SSOToken createSSOToken(HttpServletRequest request)
107            throws SSOException {
108        try {
109            SessionID sid = new SessionID(request);
110            Session session = sessionCache.getSession(sid);
111            if (sid != null) {
112                Boolean cookieMode = sid.getCookieMode();
113                if (debug.messageEnabled()) {
114                    debug.message("cookieMode is :" + cookieMode);
115                }
116                if (cookieMode != null) {
117                    session.setCookieMode(cookieMode);
118                }
119            }
120            if (checkIP && !isIPValid(session, ClientUtils.getClientIPAddress(request))) {
121                throw new Exception(SSOProviderBundle.getString("invalidIP"));
122            }
123            SSOToken ssoToken = new SSOTokenImpl(session);
124            return ssoToken;
125        } catch (Exception e) {
126            if (debug.messageEnabled()) {
127                debug.message("could not create SSOToken from HttpRequest ("
128                        + e.getMessage()
129                        + ")");
130            }
131            throw new SSOException(e);
132        }
133    }
134
135    /**
136     * Creates a single sign on token with user or service as the entity
137     *
138     * @param user     Principal representing a user or service
139     * @param password password string.
140     * @return single sign on token
141     * @throws SSOException                  if the single sign on token cannot be created.
142     * @throws UnsupportedOperationException Thrown to indicate that the
143     *                                       requested operation is not supported.
144     * @deprecated This method has been deprecated. Please use the
145     * regular LDAP authentication mechanism instead. More information
146     * on how to use the authentication programming interfaces as well as the
147     * code samples can be obtained from the "Authenticating Using
148     * OpenAM Java SDK" chapter of the OpenAM Developer's Guide.
149     */
150    public SSOToken createSSOToken(java.security.Principal user, String password)
151            throws SSOException, UnsupportedOperationException {
152        try {
153            SSOTokenImpl ssoToken = new SSOTokenImpl(user, password);
154            if (debug.messageEnabled()) {
155                debug.message("SSO token ldap auth successful for "
156                        + user.toString());
157            }
158            return ssoToken;
159        } catch (Exception e) {
160            if (debug.messageEnabled()) {
161                debug.message("could not create SSOToken for user \""
162                        + user.getName()
163                        + "\"", e);
164            }
165            throw new SSOException(e);
166        }
167    }
168
169    /**
170     * Creates a single sign on token. Note: this method should remain private
171     * and get called only by the AuthContext API. Note also: this method may reset
172     * the idle time of the session.
173     *
174     * @param tokenId       single sign on token ID.
175     * @param invokedByAuth boolean flag indicating that this method has
176     *                      been invoked by the AuthContext.getSSOToken() API.
177     * @return single sign on token.
178     * @throws SSOException                  if the single sign on token cannot be created.
179     * @throws UnsupportedOperationException Thrown to indicate that the
180     *                                       requested operation is not supported.
181     */
182    public SSOToken createSSOToken(String tokenId, boolean invokedByAuth)
183            throws SSOException, UnsupportedOperationException {
184        return createSSOToken(tokenId, invokedByAuth, true);
185    }
186
187    /**
188     * Creates a single sign on token.
189     *
190     * @param tokenId       single sign on token ID.
191     * @param invokedByAuth boolean flag indicating that this method has been invoked by the AuthContext.getSSOToken()
192     * API.
193     * @param possiblyResetIdleTime If true, the idle time of the token/session may be reset to zero.  If false, the
194     * idle time will never be reset.
195     * @return single sign on token.
196     * @throws SSOException if the single sign on token cannot be created for any reason.
197     * @throws java.lang.UnsupportedOperationException only here to satisfy the interface, this is never thrown.
198     */
199    public SSOToken createSSOToken(String tokenId, boolean invokedByAuth, boolean possiblyResetIdleTime)
200            throws SSOException, UnsupportedOperationException {
201
202        try {
203            SessionID sessionId = new SessionID(tokenId);
204            sessionId.setComingFromAuth(invokedByAuth);
205            Session session = sessionCache.getSession(sessionId, false, possiblyResetIdleTime);
206            SSOToken ssoToken = new SSOTokenImpl(session);
207            return ssoToken;
208        } catch (Exception e) {
209            if (debug.messageEnabled()) {
210                debug.message("SSOProviderImpl.createSSOToken(tokenId, "
211                        + invokedByAuth
212                        + ", "
213                        + possiblyResetIdleTime
214                        + ") could not create SSOToken for token ID \""
215                        + tokenId
216                        + "\" ("
217                        + e.getMessage()
218                        + ")");
219            }
220            throw new SSOException(e);
221        }
222    }
223
224    /**
225     * Creates a single sign on token.
226     *
227     * @param tokenId single sign on token ID.
228     * @return single sign on token.
229     * @throws SSOException                  if the single sign on token cannot be created.
230     * @throws UnsupportedOperationException
231     * @deprecated Use #createSSOToken(String, String)
232     */
233    public SSOToken createSSOToken(String tokenId)
234            throws SSOException,
235            UnsupportedOperationException {
236        return createSSOToken(tokenId, false);
237    }
238
239    /**
240     * Creates a single sign on token.
241     *
242     * @param tokenId  single sign on token ID.
243     * @param clientIP client IP address
244     * @return single sign on token.
245     * @throws SSOException                  if the single sign on token cannot be created.
246     * @throws UnsupportedOperationException Thrown to indicate that the
247     *                                       requested operation is not supported.
248     * @deprecated Use #createSSOToken(String, String)
249     */
250    public SSOToken createSSOToken(String tokenId, String clientIP)
251            throws SSOException, UnsupportedOperationException {
252        try {
253            SessionID sessionId = new SessionID(tokenId);
254            Session session = sessionCache.getSession(sessionId);
255            if (checkIP && !isIPValid(session, clientIP)) {
256                throw new Exception(SSOProviderBundle.getString("invalidIP"));
257            }
258            SSOToken ssoToken = new SSOTokenImpl(session);
259            return ssoToken;
260        } catch (Exception e) {
261            if (debug.messageEnabled()) {
262                debug.message("could not create SSOToken for token ID \""
263                        + tokenId
264                        + "\" ("
265                        + e.getMessage()
266                        + ")");
267            }
268            throw new SSOException(e);
269        }
270    }
271
272    /**
273     * Checks the validity of the single sign on token
274     *
275     * @param token The single sign on token object to be validated
276     * @return Returns true if the <code>SSOToken</code> is valid
277     */
278    @Override
279    public boolean isValidToken(SSOToken token) {
280        return isValidToken(token, true);
281    }
282
283    /**
284     * Checks the validity of the single sign on token
285     *
286     * @param token   The single sign on token object to be validated
287     * @param refresh Flag indicating whether refreshing the token is allowed
288     * @return Returns true if the <code>SSOToken</code> is valid, false otherwise
289     */
290    @Override
291    public boolean isValidToken(SSOToken token, boolean refresh) {
292        /*
293         * If the token was created from createSSOToken(Principal, password)
294         * there is no association with session. Use this temp solution for now.
295         * If this method is going to go away, we can remove that method, otherwise
296         * a better mechanism has to be implemented.
297         */
298        SSOTokenImpl tokenImpl = (SSOTokenImpl) token;
299        return (tokenImpl.isValid(refresh));
300    }
301
302    /**
303     * Checks if the single sign on token is valid.
304     *
305     * @param token single sign on token.
306     * @throws SSOException if the single sign on token is not valid.
307     */
308    public void validateToken(SSOToken token) throws SSOException {
309        try {
310            /*
311             * if the token was created from createSSOToken(Principal, password)
312             * there is no association with session. Use this temp solution now.
313             * if this method is going to go away, we can remove that method.
314             * otherwise a better mechanism has to be implemented.
315             */
316            SSOTokenImpl tokenImpl = (SSOTokenImpl) token;
317            tokenImpl.validate();
318        } catch (Exception e) {
319            if (debug.messageEnabled()) {
320                debug.message("validateToken: ", e);
321            }
322            throw new SSOException(SSOProviderBundle.rbName, "invalidtoken", null);
323        }
324    }
325
326    /**
327     * Destroys a single sign on token
328     *
329     * @param token The single sign on token object to be destroyed
330     * @throws SSOException if the given token cannot be destroyed
331     */
332    public void destroyToken(SSOToken token) throws SSOException {
333        try {
334            SSOTokenImpl tokenImpl = (SSOTokenImpl) token;
335            if (tokenImpl.isLdapConnection() == true) {
336                tokenImpl.setStatus(false);
337                return;
338            }
339            SSOTokenID tokenid = token.getTokenID();
340            String id = tokenid.toString();
341            SessionID sessid = new SessionID(id);
342            Session session = sessionCache.getSession(sessid);
343            session.destroySession(session);
344        } catch (Exception e) {
345            if (debug.messageEnabled()) {
346                debug.message("DestroyToken: ", e);
347            }
348            throw new SSOException(e);
349        }
350    }
351
352    @Override
353    public void logout(final SSOToken token) throws SSOException {
354        try {
355            Session session = sessionCache.getSession(new SessionID(token.getTokenID().toString()));
356            session.logout();
357        } catch (SessionException e) {
358            if (debug.messageEnabled()) {
359                debug.message("Logout: ", e);
360            }
361            throw new SSOException(e);
362        }
363    }
364
365    /**
366     * Validate the IP address of the client with the IP stored in Session.
367     *
368     * @param sess     Session object associated with the token
369     * @param clientIP IP address of the current client who made
370     *                 <code>HttpRequest</code>.
371     * @return Returns true if the IP is valid else false.
372     * @throws SSOException if IP cannot be validated for the given session
373     */
374    public boolean isIPValid(Session sess, String clientIP) throws SSOException {
375        boolean check = false;
376        try {
377            InetAddress sessIPAddress = InetAddress.getByName(sess
378                    .getProperty("Host"));
379            InetAddress clientIPAddress = InetAddress.getByName(clientIP);
380            if (sessIPAddress.equals(clientIPAddress)) {
381                check = true;
382            }
383        } catch (Exception e) {
384            if (debug.messageEnabled()) {
385                debug.message("IP address check of Token Failed", e);
386            }
387        }
388        return check;
389    }
390
391    /**
392     * Refresh the Session corresponding to the single sign on token from the
393     * Session Server.
394     *
395     * @param token single sign on token for which session need to be refreshed
396     * @throws SSOException if the session cannot be refreshed
397     */
398    @Override
399    public void refreshSession(SSOToken token) throws SSOException {
400        refreshSession(token, true);
401    }
402
403    /**
404     * Refresh the Session corresponding to the single sign on token from the
405     * Session Server.
406     *
407     * @param token single sign on token for which session need to be refreshed.
408     * @param possiblyResetIdleTime if true, the idle time may be reset, if false it will never be.
409     * @throws SSOException if the session cannot be refreshed.
410     */
411    @Override
412    public void refreshSession(SSOToken token, boolean possiblyResetIdleTime) throws SSOException {
413        try {
414            SSOTokenID tokenId = token.getTokenID();
415            SessionID sid = new SessionID(tokenId.toString());
416            Session session = sessionCache.getSession(sid);
417            session.refresh(possiblyResetIdleTime);
418        } catch (Exception e) {
419            debug.error("Error in refreshing the session from sessions server");
420            throw new SSOException(e);
421        }
422    }
423
424    /**
425     * Destroys a single sign on token.
426     *
427     * @param destroyer
428     *            The single sign on token object used to authorize the
429     *            operation
430     * @param destroyed
431     *            The single sign on token object to be destroyed.
432     * @throws SSOException
433     *             if the there was an error during communication with session
434     *             service.
435     *
436     * @supported.api
437     */
438    public void destroyToken(SSOToken destroyer, SSOToken destroyed)
439            throws SSOException {
440        try {
441            Session requester = ((SSOTokenImpl) destroyer).getSession();
442            Session target = ((SSOTokenImpl) destroyed).getSession();
443            requester.destroySession(target);
444        } catch (SessionException e) {
445            throw new SSOException(e);
446        }
447    }
448
449    /**
450     * Returns a list of single sign on token objects
451     * which correspond to valid Sessions accessible to requester. single sign
452     * on token objects returned are restricted: they can only be used to
453     * retrieve properties and destroy sessions they represent.
454     *
455     * @param requester
456     *            The single sign on token object used to authorize the
457     *            operation
458     * @param server
459     *            The server for which the valid sessions are to be retrieved
460     * @return Set of Valid Sessions
461     * @throws SSOException
462     *             if the there was an error during communication with session
463     *             service.
464     *
465     * @supported.api
466     */
467    public Set getValidSessions(SSOToken requester, String server)
468            throws SSOException {
469        Set results = new HashSet();
470        try {
471            SearchResults result = ((SSOTokenImpl) requester).getSession().getValidSessions(server, null);
472
473            for (Iterator iter = result.getResultAttributes().values().iterator(); iter.hasNext();) {
474                Session s = (Session) iter.next();
475                if (s != null) {
476                    results.add(new SSOTokenImpl(s));
477                }
478            }
479        } catch (SessionException e) {
480            throw new SSOException(e);
481        }
482        return results;
483    }
484}