001/*
002 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003 *
004 * Copyright (c) 2006 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: SecurityTokenManagerClient.java,v 1.9 2008/08/19 19:11:09 veiming Exp $
026 *
027 * Portions Copyright 2013-2015 ForgeRock AS.
028 */
029
030package com.sun.identity.liberty.ws.security;
031
032import com.sun.identity.shared.locale.Locale;
033import com.sun.identity.shared.configuration.SystemPropertiesManager;
034import com.sun.identity.shared.xml.XMLUtils;
035
036import com.sun.identity.common.SystemConfigurationUtil;
037import com.sun.identity.shared.encode.Base64;
038import com.sun.identity.shared.encode.Base64;
039
040import com.sun.identity.liberty.ws.common.wsse.BinarySecurityToken;
041import com.sun.identity.liberty.ws.disco.EncryptedResourceID;
042
043import com.sun.identity.plugin.session.SessionManager;
044import com.sun.identity.saml.assertion.Assertion;
045import com.sun.identity.saml.assertion.AssertionIDReference;
046import com.sun.identity.saml.assertion.Attribute;
047import com.sun.identity.saml.assertion.NameIdentifier;
048import com.sun.identity.saml.common.SAMLConstants;
049import com.sun.identity.saml.common.SAMLException;
050import com.sun.identity.saml.common.SAMLUtils;
051import com.sun.identity.shared.jaxrpc.SOAPClient;
052import com.sun.identity.saml.protocol.AssertionArtifact;
053
054import java.net.InetAddress;
055import java.net.URL;
056
057import java.rmi.RemoteException;
058import java.rmi.ServerException;
059
060import java.security.cert.X509Certificate;
061
062import java.util.Iterator;
063import java.util.ResourceBundle;
064
065import javax.xml.soap.SOAPException;
066
067/**
068 * The class <code>SecurityTokenManagerClient</code> is a <code>final</code> 
069 * class that provides interfaces to create, get and destroy
070 * <code>Assertion</code>s.
071 * <p>
072 * The class provides mechanisms to manage the <code>Assertion</code>s either
073 * locally (i.e., within the same JVM process) or remotely on another instance
074 * of OpenAM. The default constructor will manage the <code>
075 * Assertion</code>s locally if it detects SAML web services running locally,
076 * else will use on of the configured OpenAM. The constructor which
077 * accepts an <code>URL</code> will always use the URL to manage the assertions.
078 * <p>
079 * Having obtained an instance of <code>AssertionManagerClient</code>, its
080 * methods can be called to create/get <code>Assertion</code>, and 
081 * <code>AssertionArtifact</code>, and to obtain decision from an 
082 * <code>Query</code>.
083 *
084 * @supported.api
085 */
086public final class SecurityTokenManagerClient {
087
088    // Service name in naming
089    private static String SERVICE_NAME = "securitytokenmanager";
090
091    // Flag to determine if AssertionManager is local or remote
092    private static boolean checkedForLocal;
093    private static boolean isLocal;
094    private boolean useLocal;
095
096    // Remote JAX-RPC server for objects that use default constructor
097    private static SOAPClient remoteStub;
098
099    // If local pointer to SecurityTokenManager instance
100    private SecurityTokenManager securityTokenManager;
101    private String ssoToken = null;
102
103    // JAX-RPC remote stub
104    private SOAPClient stub;
105
106    static ResourceBundle bundle = 
107                Locale.getInstallResourceBundle("libLibertySecurity");
108    /**
109     * Returns an instance of <code>SecurityTokenManagerClient</code>
110     *
111     * @param credential credential of the caller used to see
112     *        if access to this security token manager client is allowed.
113     * @throws SecurityTokenException if unable to access the
114     *         the security token manager client.
115     */
116    public SecurityTokenManagerClient(Object credential)
117                                      throws SecurityTokenException {
118        if (!checkedForLocal) {
119            try {
120                // Construct the URL for local server.
121                remoteStub = getServiceEndPoint(
122                    SystemPropertiesManager.get(SAMLConstants.SERVER_PROTOCOL),
123                    SystemPropertiesManager.get(SAMLConstants.SERVER_HOST),
124                    SystemPropertiesManager.get(SAMLConstants.SERVER_PORT),
125                    SystemPropertiesManager.get(SAMLConstants.SERVER_URI));
126                remoteStub.send("checkForLocal", null, null, null);
127                if (SecurityTokenManagerImpl.isLocal) {
128                    isLocal = true;
129                    SecurityTokenManager.debug.warning(
130                            "STMC(): Using local service");
131                    securityTokenManager = new SecurityTokenManager(credential);
132                }
133                checkedForLocal = true;
134            } catch (Exception e) {
135                checkedForLocal = true;
136                if (SecurityTokenManager.debug.warningEnabled()) {
137                    SecurityTokenManager.debug.warning(
138                            "SecurityTokenManagerClient()Exception", e);
139                }
140                throw (new SecurityTokenException(e.getMessage()));
141            }
142        }
143        if (isLocal) {
144            useLocal = true;
145        } else {
146            // Use the remoteStub if set
147            stub = remoteStub;
148            try {
149                ssoToken =
150                    SessionManager.getProvider().getSessionID(credential);
151                stub.send("initialization", ssoToken, null, ssoToken); 
152            } catch (Exception e) {
153                if (SecurityTokenManager.debug.warningEnabled()) {
154                    SecurityTokenManager.debug.warning(
155                            "SecurityTokenManagerClient()Exception", e);
156                }
157                throw (new SecurityTokenException(e.getMessage()));
158            }
159        }
160    }
161    
162    /**
163     * Returns an instance of <code>SecurityTokenManagerClient</code>
164     * that will use the provided <code>URL</code> for the management
165     * of security tokens.
166     *
167     * @param url the <code>SecurityTokenManagerClient</code> service URL that
168     *        will be used to get <code>BinarySecurityToken</code> and
169     *        <code>SAMLSecurityToken</code>.
170     * @param credential credential of the caller used to see
171     *        if access to this security token manager client is allowed.
172     * @throws SecurityTokenException if unable to access the
173     *         the security token manager client.
174     */
175    public SecurityTokenManagerClient(String url, Object credential)
176        throws SecurityTokenException {
177        try {
178            // Construct the JAX-RPC stub and set the URL endpoint
179            ssoToken = SessionManager.getProvider().getSessionID(credential);
180            String[] urls = {url};
181            stub = new SOAPClient(urls);
182            stub.send("initialization", ssoToken, null, ssoToken);
183            useLocal = false;
184        } catch (Exception e) {
185            if (SecurityTokenManager.debug.warningEnabled()) {
186                SecurityTokenManager.debug.warning("STMC() Exception", e);
187            }
188            throw (new SecurityTokenException(e.getMessage()));
189        }
190    }
191
192    // Private method to get the service endpoint URL
193    private static SOAPClient getServiceEndPoint(String protocol,
194        String hostname, String port, String uri) throws Exception {
195        // Obtain the URL for the service endpoint
196        int intPort = Integer.parseInt(port);
197        URL weburl = SystemConfigurationUtil.getServiceURL(SERVICE_NAME,
198            protocol, hostname, intPort, uri);
199        String iurl = weburl.toString();
200        if (SecurityTokenManager.debug.messageEnabled()) {
201            SecurityTokenManager.debug.message(
202                "SecurityTokenManagerClient with URL: " + iurl);
203        }
204        String[] urls = {iurl};
205        return new SOAPClient(urls);
206    }
207
208    private static SOAPClient getRemoteStub()
209        throws SecurityTokenException {
210        boolean foundServer = false;
211        Exception ee = null;
212        SOAPClient remoteStub = null;
213        try {
214            // Get the list of platform servers
215            Iterator serverList =
216                SystemConfigurationUtil.getServerList().iterator();
217
218            // Get a server that is responding
219            while (serverList.hasNext() && !foundServer) {
220                URL u = new URL((String) serverList.next());
221                remoteStub = getServiceEndPoint(u.getProtocol(), u.getHost(),
222                    Integer.toString(u.getPort()), u.getPath());
223                // Check if the server is active
224                try {
225                    // this call will throw an exception if server is down
226                    remoteStub.send("checkForLocal", null, null, null);
227                    if (SecurityTokenManager.debug.messageEnabled()) {
228                        SecurityTokenManager.debug.message(
229                            "STMC(): Using the remote URL: " + u.toString());
230                    }
231                    foundServer = true;
232                    if (SecurityTokenManager.debug.warningEnabled()) {
233                        SecurityTokenManager.debug.warning(
234                            "STMC:getRemoteStub: remote server being used: "
235                            + u.toString());
236                    }
237                } catch (Exception e) {
238                    ee = e;
239                    if (SecurityTokenManager.debug.warningEnabled()) {
240                        SecurityTokenManager.debug.warning(
241                            "STMC:getRemoteStub: server (" +
242                            u.toString() + ") error: ", e);
243                    }
244                }
245            }
246        } catch (Exception f) {
247            ee = f;
248            if (SecurityTokenManager.debug.warningEnabled()) {
249                SecurityTokenManager.debug.warning(
250                        "STMC:getRemoteStub: generic error: ", f);
251            }
252        }
253        
254        if (!foundServer) {
255            // No valid server found. Return the last exception
256            if (ee != null) {
257                throw (new SecurityTokenException(ee.getMessage()));
258            } else {
259                throw (new SecurityTokenException(
260                    bundle.getString("serverNotFound")));
261            }
262        }
263        return (remoteStub);
264    }
265
266    /**
267     * Sets the alias of the certificate used for issuing <code>WSS</code>
268     * token, i.e. <code>WSS</code> <code>X509</code> Token, <code>WSS</code>
269     * SAML Token. If the <code>certAlias</code> is never set, a default
270     * certificate will be used for issuing <code>WSS</code> tokens.
271     *
272     * @param certAlias String alias name for the certificate.
273     * @throws SecurityTokenException if certificate for the
274     *            <code>certAlias</code> could not be found in key store.
275     * 
276     * @supported.api
277     */
278    public void setCertAlias(java.lang.String certAlias)
279        throws SecurityTokenException {
280        if (useLocal) {
281            securityTokenManager.setCertAlias(certAlias);
282        } else {
283            try {
284                Object[] obj = {certAlias, Boolean.TRUE};
285                stub.send("setCertificate", obj, null, ssoToken);
286            } catch (Exception e) {
287                if (SecurityTokenManager.debug.warningEnabled()) {
288                    SecurityTokenManager.debug.warning(
289                        "STMC:setCertAlias()", e);
290                }
291                throw (new SecurityTokenException(e.getMessage()));
292            }
293        }
294    }
295
296    /**
297     * Sets the  certificate used for issuing <code>WSS</code> token, i.e.
298     * <code>WSS</code> <code>X509</code> Token, <code>WSS</code> SAML Token.
299     * If the certificate is never set, a default certificate will
300     * be used for issuing <code>WSS</code> tokens
301     *
302     * @param cert <code>X509</code> certificate
303     * @throws SecurityTokenException if could not set Certificate.
304     */
305    public void setCertificate(X509Certificate cert)
306        throws SecurityTokenException {
307        if (useLocal) {
308            securityTokenManager.setCertificate(cert);
309        } else {
310            try {
311                String certString = Base64.encode(cert.getEncoded());
312                Object[] obj = {certString, Boolean.FALSE};
313                stub.send("setCertificate", obj, null, ssoToken);
314            } catch (Exception e) {
315                if (SecurityTokenManager.debug.warningEnabled()) {
316                    SecurityTokenManager.debug.warning(
317                        "STMC:setCertificate()", e);
318                }
319                throw (new SecurityTokenException(e.getMessage()));
320            }
321        }
322    }
323
324    /**
325     * Gets the <code>X509</code> certificate Token.
326     *
327     * @return <code>X509</code> certificate Token.
328     * @throws SecurityTokenException if the binary security token could
329     * not be obtained.
330     */
331    public BinarySecurityToken getX509CertificateToken()
332                                      throws SecurityTokenException {
333        if (useLocal) {
334            return securityTokenManager.getX509CertificateToken();
335        } 
336
337        String bst = null;
338        try {
339            bst = (String) stub.send("getX509CertificateToken", null, null,
340                ssoToken);
341            return (new BinarySecurityToken(XMLUtils.toDOMDocument(bst,
342                    SecurityTokenManager.debug).getDocumentElement()));
343        } catch (Exception e) {
344            if (SecurityTokenManager.debug.warningEnabled()) {
345                SecurityTokenManager.debug.warning(
346                    "STMC:getX509CertificateToken()", e);
347            }
348            throw (new SecurityTokenException(e.getMessage()));
349        }
350    }
351
352    /**
353     * Creates a SAML Assertion for message authentication.
354     *
355     * @param senderIdentity name identifier of the sender.
356     * @return Assertion which contains an <code>AuthenticationStatement</code>.
357     * @throws SecurityTokenException if the assertion could not be obtained.
358     * @throws SAMLException if unable to generate the SAML Assertion. 
359     */
360    public SecurityAssertion getSAMLAuthenticationToken(
361            NameIdentifier senderIdentity)
362            throws SecurityTokenException, SAMLException {
363        if (useLocal) {
364            return (securityTokenManager.getSAMLAuthenticationToken(
365                        senderIdentity));
366        }
367
368        try {
369            String ni = senderIdentity.toString(true, true);
370            String assertion = (String) stub.send("getSAMLAuthenticationToken", 
371                ni, null, ssoToken);
372            return (new SecurityAssertion(XMLUtils.toDOMDocument(assertion,
373                    SecurityTokenManager.debug).getDocumentElement()));
374        } catch (Exception re) {
375            if (SecurityTokenManager.debug.warningEnabled()) {
376                SecurityTokenManager.debug.warning(
377                    "STMC:getSAMLAuthenticationToken()", re);
378            }
379            throw (new SAMLException(re.getMessage()));
380        }
381    }
382
383    /**
384     * Creates a SAML Assertion for message authorization, the assertion could
385     * optionally contain an <code>AuthenticationStatement</code> which will be
386     * used for message authentication.
387     *
388     * @param senderIdentity name identifier of the sender.
389     * @param invocatorSession <code>SessionContext</code> of  the invocation 
390     *        identity, it is normally obtained by the credential reference in 
391     *        the SAML <code>AttributeDesignator</code> for discovery resource 
392     *        offering which is part of the liberty <code>ID-FF</code> 
393     *        <code>AuthenResponse</code>.
394     * @param resourceID id for the resource to be accessed.
395     * @param includeAuthN if true, include an 
396     *        <code>AutheticationStatement</code> in the Assertion which will be 
397     *        used for message authentication. 
398     * @param includeResourceAccessStatement if true, a 
399     *        <code>ResourceAccessStatement</code>
400     *        will be included in the Assertion 
401     *        (for <code>AuthorizeRequester</code> directive). If false, 
402     *        a <code>SessionContextStatement</code> will be included in the 
403     *        Assertion (for <code>AuthenticationSessionContext</code> 
404     *        directive). In the case when both <code>AuthorizeRequester</code> 
405     *        and <code>AuthenticationSessionContext</code> directive need to be
406     *        handled, use "true" as parameter here since the 
407     *        <code>SessionContext</code> will always be included in the 
408     *        <code>ResourceAccessStatement</code>.
409     * @param recipientProviderID recipient's provider ID.
410     * @return the <code>SecurityAssertion</code> object.
411     * @throws SecurityTokenException if the assertion could not be obtained.
412     * @throws SAMLException if unable to generate the SAML Assertion.
413     */
414    public SecurityAssertion getSAMLAuthorizationToken(
415            NameIdentifier senderIdentity,
416            SessionContext invocatorSession,
417            String resourceID,
418            boolean includeAuthN,
419            boolean includeResourceAccessStatement,
420            String recipientProviderID)
421            throws SecurityTokenException, SAMLException {
422        if (useLocal) {
423            return (securityTokenManager.getSAMLAuthorizationToken(
424                        senderIdentity, invocatorSession, resourceID,
425                        includeAuthN, includeResourceAccessStatement,
426                        recipientProviderID));
427        }
428
429        try {
430            String ni = senderIdentity.toString(true, true);
431            String sc = invocatorSession.toXMLString(true, true);
432            Object[] obj = {ni, sc, resourceID, Boolean.FALSE, 
433                Boolean.valueOf(includeAuthN),
434                Boolean.valueOf(includeResourceAccessStatement),
435                recipientProviderID};
436            String assertion = (String) stub.send("getSAMLAuthorizationToken",
437                obj, null, ssoToken);
438            return (new SecurityAssertion(XMLUtils.toDOMDocument(assertion,
439                    SecurityTokenManager.debug).getDocumentElement()));
440        } catch (Exception e) {
441            if (SecurityTokenManager.debug.warningEnabled()) {
442                SecurityTokenManager.debug.warning(
443                        "STMC:createAssertionArtifact:", e);
444            }
445            throw (new SecurityTokenException(e.getMessage()));
446        }
447    }
448
449    /**
450     * Creates a SAML Assertion for message authorization, the assertion could
451     * optionally contain an <code>AuthenticationStatement</code> which will be
452     * used for message authentication.
453     *
454     * @param senderIdentity name identifier of the sender.
455     * @param invocatorSession <code>SessionContext</code> of  the invocation 
456     *        identity, it is normally obtained by the credential reference in
457     *        the SAML <code>AttributeDesignator</code> for discovery resource   
458     *        offering which is part of the liberty <code>ID-FF</code> 
459     *        <code>AuthenResponse</code>.
460     * @param encResourceID Encrypted ID for the resource to be accessed.
461     * @param includeAuthN if true, include an 
462     *        <code>AutheticationStatement</code> in the
463     *        Assertion which will be used for message authentication. 
464     * @param includeResourceAccessStatement if true, 
465     *        a <code>ResourceAccessStatement</code> will be included in the 
466     *        Assertion (for <code>AuthorizeRequester</code> directive). If
467     *        false, a <code>SessionContextStatement</code> will be included in 
468     *        the Assertion (for <code>AuthenticationSessionContext</code> 
469     *        directive). In the case when both <code>AuthorizeRequester</code> 
470     *        and <code>AuthenticationSessionContext</code> directive need to be
471     *        handled, use "true" as parameter here since the 
472     *        <code>SessionContext</code> will always be included in the 
473     *        <code>ResourceAccessStatement</code>.
474     * @param recipientProviderID recipient's provider ID.
475     * @return the <code>SecurityAssertion</code> object.
476     * @throws SecurityTokenException if the assertion could not be obtained.
477     * @throws SAMLException if unable to generate the SAML Assertion.
478     */
479    public SecurityAssertion getSAMLAuthorizationToken(
480                NameIdentifier senderIdentity,
481                SessionContext invocatorSession,
482                EncryptedResourceID encResourceID,
483                boolean includeAuthN,
484                boolean includeResourceAccessStatement,
485                String recipientProviderID)
486        throws SecurityTokenException, SAMLException {
487        if (useLocal) {
488            return (securityTokenManager.getSAMLAuthorizationToken(
489                senderIdentity, invocatorSession, encResourceID,
490                includeAuthN, includeResourceAccessStatement,
491                recipientProviderID));
492        }
493
494        String assertion = null;
495
496        try {
497            String ni = senderIdentity.toString(true, true);
498            String sc = invocatorSession.toXMLString(true, true);
499            String resourceID = encResourceID.toString();
500            Object[] obj = {ni, sc, resourceID,  Boolean.TRUE,
501                Boolean.valueOf(includeAuthN),
502                Boolean.valueOf(includeResourceAccessStatement),
503                recipientProviderID};
504            assertion = (String) stub.send("getSAMLAuthorizationToken",
505                obj, null, ssoToken);
506            return (new SecurityAssertion(XMLUtils.toDOMDocument(assertion,
507                    SecurityTokenManager.debug).getDocumentElement()));
508        } catch (Exception e) {
509            if (SecurityTokenManager.debug.warningEnabled()) {
510                SecurityTokenManager.debug.warning(
511                        "STMC:getSAMLAuthorizationToken() ", e);
512            }
513            throw (new SecurityTokenException(e.getMessage()));
514        }
515    }
516}