KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jivesoftware > smack > SASLAuthentication


1 /**
2  * $RCSfile$
3  * $Revision: $
4  * $Date: $
5  *
6  * Copyright 2003-2004 Jive Software.
7  *
8  * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  */

20
21 package org.jivesoftware.smack;
22
23 import org.jivesoftware.smack.filter.PacketIDFilter;
24 import org.jivesoftware.smack.packet.Bind;
25 import org.jivesoftware.smack.packet.IQ;
26 import org.jivesoftware.smack.packet.Session;
27 import org.jivesoftware.smack.sasl.SASLMechanism;
28 import org.jivesoftware.smack.sasl.SASLPlainMechanism;
29
30 import java.io.IOException JavaDoc;
31 import java.lang.reflect.Constructor JavaDoc;
32 import java.util.*;
33
34 /**
35  * This class is responsible authenticating the user using SASL, binding the resource
36  * to the connection and establishing a session with the server.<p>
37  *
38  * Once TLS has been negotiated (i.e. the connection has been secured) it is possible to
39  * register with the server, authenticate using Non-SASL or authenticate using SASL. If the
40  * server supports SASL then Smack will first try to authenticate using SASL. But if that
41  * fails then Non-SASL will be tried.<p>
42  *
43  * The server may support many SASL mechanisms to use for authenticating. Out of the box
44  * Smack provides SASL PLAIN but it is possible to register new SASL Mechanisms. Use
45  * {@link #registerSASLMechanism(int, String, Class)} to add new mechanisms. See
46  * {@link SASLMechanism}.<p>
47  *
48  * Once the user has been authenticated with SASL, it is necessary to bind a resource for
49  * the connection. If no resource is passed in {@link #authenticate(String, String, String)}
50  * then the server will assign a resource for the connection. In case a resource is passed
51  * then the server will receive the desired resource but may assign a modified resource for
52  * the connection.<p>
53  *
54  * Once a resource has been binded and if the server supports sessions then Smack will establish
55  * a session so that instant messaging and presence functionalities may be used.
56  *
57  * @author Gaston Dombiak
58  */

59 public class SASLAuthentication implements UserAuthentication {
60
61     private static Map implementedMechanisms = new HashMap();
62     private static List mechanismsPreferences = new ArrayList();
63
64     private XMPPConnection connection;
65     private Collection serverMechanisms = new ArrayList();
66     private SASLMechanism currentMechanism = null;
67     /**
68      * Boolean indicating if SASL negotiation has finished and was successful.
69      */

70     private boolean saslNegotiated = false;
71     private boolean sessionSupported = false;
72
73     static {
74         // Register SASL mechanisms supported by Smack
75
registerSASLMechanism(0, "PLAIN", SASLPlainMechanism.class);
76     }
77
78     /**
79      * Registers a new SASL mechanism in the specified preference position. The client will try
80      * to authenticate using the most prefered SASL mechanism that is also supported by the server.
81      * <p/>
82      * <p/>
83      * Use the <tt>index</tt> parameter to set the level of preference of the new SASL mechanism.
84      * A value of 0 means that the mechanism is the most prefered one.
85      *
86      * @param index preference position amongst all the implemented SASL mechanism. Starts with 0.
87      * @param name common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or KERBEROS_V4.
88      * @param mClass a SASLMechanism subclass.
89      */

90     public static void registerSASLMechanism(int index, String JavaDoc name, Class JavaDoc mClass) {
91         implementedMechanisms.put(name, mClass);
92         mechanismsPreferences.add(index, name);
93     }
94
95     /**
96      * Unregisters an existing SASL mechanism. Once the mechanism has been unregistered it won't
97      * be possible to authenticate users using the removed SASL mechanism.
98      *
99      * @param name common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or KERBEROS_V4.
100      */

101     public static void unregisterSASLMechanism(String JavaDoc name) {
102         implementedMechanisms.remove(name);
103         mechanismsPreferences.remove(name);
104     }
105
106     /**
107      * Returns the registerd SASLMechanism classes sorted by the level of preference.
108      *
109      * @return the registerd SASLMechanism classes sorted by the level of preference.
110      */

111     public static List getRegisterSASLMechanisms() {
112         List answer = new ArrayList();
113         for (Iterator it = mechanismsPreferences.iterator(); it.hasNext();) {
114             answer.add(implementedMechanisms.get(it.next()));
115         }
116         return answer;
117     }
118
119     SASLAuthentication(XMPPConnection connection) {
120         super();
121         this.connection = connection;
122     }
123
124     public String JavaDoc authenticate(String JavaDoc username, String JavaDoc password, String JavaDoc resource)
125             throws XMPPException {
126         // Locate the SASLMechanism to use
127
Class JavaDoc selected = null;
128         for (Iterator it = mechanismsPreferences.iterator(); it.hasNext();) {
129             String JavaDoc mechanism = (String JavaDoc) it.next();
130             if (implementedMechanisms.containsKey(mechanism) &&
131                     serverMechanisms.contains(mechanism)) {
132                 selected = (Class JavaDoc) implementedMechanisms.get(mechanism);
133                 break;
134             }
135         }
136         if (selected != null) {
137             // A SASL mechanism was found. Authenticate using the selected mechanism and then
138
// proceed to bind a resource
139
try {
140                 Constructor JavaDoc constructor = selected
141                         .getConstructor(new Class JavaDoc[]{SASLAuthentication.class});
142                 currentMechanism = (SASLMechanism) constructor.newInstance(new Object JavaDoc[]{this});
143                 // Trigger SASL authentication with the selected mechanism
144
currentMechanism.authenticate(username, connection.getServiceName(), password);
145
146                 // Wait until SASL negotiation finishes
147
synchronized (this) {
148                     try {
149                         wait(30000);
150                     } catch (InterruptedException JavaDoc e) {
151                     }
152                 }
153
154                 if (saslNegotiated) {
155                     // We now need to bind a resource for the connection
156
// Open a new stream and wait for the response
157
connection.packetWriter.openStream();
158
159                     // Wait until server sends response containing the <bind> element
160
synchronized (this) {
161                         try {
162                             wait(30000);
163                         } catch (InterruptedException JavaDoc e) {
164                         }
165                     }
166
167                     Bind bindResource = new Bind();
168                     bindResource.setResource(resource);
169
170                     PacketCollector collector = connection
171                             .createPacketCollector(new PacketIDFilter(bindResource.getPacketID()));
172                     // Send the packet
173
connection.sendPacket(bindResource);
174                     // Wait up to a certain number of seconds for a response from the server.
175
Bind response = (Bind) collector
176                             .nextResult(SmackConfiguration.getPacketReplyTimeout());
177                     collector.cancel();
178                     if (response == null) {
179                         throw new XMPPException("No response from the server.");
180                     }
181                     // If the server replied with an error, throw an exception.
182
else if (response.getType() == IQ.Type.ERROR) {
183                         throw new XMPPException(response.getError());
184                     }
185                     String JavaDoc userJID = response.getJid();
186
187                     if (sessionSupported) {
188                         Session session = new Session();
189                         collector = connection
190                                 .createPacketCollector(new PacketIDFilter(session.getPacketID()));
191                         // Send the packet
192
connection.sendPacket(session);
193                         // Wait up to a certain number of seconds for a response from the server.
194
IQ ack = (IQ) collector
195                                 .nextResult(SmackConfiguration.getPacketReplyTimeout());
196                         collector.cancel();
197                         if (ack == null) {
198                             throw new XMPPException("No response from the server.");
199                         }
200                         // If the server replied with an error, throw an exception.
201
else if (ack.getType() == IQ.Type.ERROR) {
202                             throw new XMPPException(ack.getError());
203                         }
204                     }
205                     return userJID;
206                 } else {
207                     // SASL authentication failed so try a Non-SASL authentication
208
return new NonSASLAuthentication(connection)
209                             .authenticate(username, password, resource);
210                 }
211
212             } catch (Exception JavaDoc e) {
213                 e.printStackTrace();
214                 // SASL authentication failed so try a Non-SASL authentication
215
return new NonSASLAuthentication(connection)
216                         .authenticate(username, password, resource);
217             }
218         } else {
219             // No SASL method was found so try a Non-SASL authentication
220
return new NonSASLAuthentication(connection).authenticate(username, password, resource);
221         }
222     }
223
224     /**
225      * Sets the available SASL mechanism reported by the server. The server will report the
226      * available SASL mechanism once the TLS negotiation was successful. This information is
227      * stored and will be used when doing the authentication for logging in the user.
228      *
229      * @param mechanisms collection of strings with the available SASL mechanism reported
230      * by the server.
231      */

232     void setAvailableSASLMethods(Collection mechanisms) {
233         this.serverMechanisms = mechanisms;
234     }
235
236     /**
237      * The server is challenging the SASL authentication we just sent. Forward the challenge
238      * to the current SASLMechanism we are using. The SASLMechanism will send a response to
239      * the server. The length of the challenge-response sequence varies according to the
240      * SASLMechanism in use.
241      *
242      * @param challenge a base64 encoded string representing the challenge.
243      * @throws IOException If a network error occures while authenticating.
244      */

245     void challengeReceived(String JavaDoc challenge) throws IOException JavaDoc {
246         currentMechanism.challengeReceived(challenge);
247     }
248
249     /**
250      * Notification message saying that SASL authentication was successful. The next step
251      * would be to bind the resource.
252      */

253     void authenticated() {
254         saslNegotiated = true;
255         synchronized (this) {
256             // Wake up the thread that is waiting in the #authenticate method
257
notify();
258         }
259     }
260
261     /**
262      * Notification message saying that the server requires the client to bind a
263      * resource to the stream.
264      */

265     void bindingRequired() {
266         synchronized (this) {
267             // Wake up the thread that is waiting in the #authenticate method
268
notify();
269         }
270     }
271
272     public void send(String JavaDoc stanza) throws IOException JavaDoc {
273         connection.writer.write(stanza);
274         connection.writer.flush();
275     }
276
277     /**
278      * Notification message saying that the server supports sessions. When a server supports
279      * sessions the client needs to send a Session packet after successfully binding a resource
280      * for the session.
281      */

282     void sessionsSupported() {
283         sessionSupported = true;
284     }
285 }
286
Popular Tags