1 /*
2  * hunt-amqp: AMQP library for D programming language, based on hunt-net.
3  *
4  * Copyright (C) 2018-2019 HuntLabs
5  *
6  * Website: https://www.huntlabs.net
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 module hunt.amqp.impl.ProtonSaslClientAuthenticatorImpl;
12 
13 import hunt.collection.Set;
14 
15 //import javax.security.sasl.AuthenticationException;
16 //import javax.security.sasl.SaslException;
17 
18 import hunt.amqp.sasl.MechanismMismatchException;
19 import hunt.amqp.sasl.ProtonSaslAuthenticator;
20 import hunt.proton.engine.Sasl;
21 import hunt.proton.engine.Transport;
22 
23 import hunt.amqp.Handler;
24 import hunt.amqp.ProtonConnection;
25 import hunt.amqp.sasl.ProtonSaslMechanism;
26 import hunt.amqp.sasl.SaslSystemException;
27 import hunt.amqp.sasl.impl.ProtonSaslMechanismFinderImpl;
28 import hunt.net.Connection;
29 import hunt.Exceptions;
30 import hunt.logging;
31 
32 alias NetSocket = hunt.net.Connection;
33 /**
34  * Manage the client side of the SASL authentication process.
35  */
36 class ProtonSaslClientAuthenticatorImpl : ProtonSaslAuthenticator {
37 
38   private Sasl sasl;
39   private  string username;
40   private  string password;
41   private ProtonSaslMechanism mechanism;
42   private Set!string mechanismsRestriction;
43   private Handler!ProtonConnection handler;
44   private NetSocket.Connection socket;
45   private ProtonConnection connection;
46   private bool _succeeded;
47 
48   /**
49    * Create the authenticator and initialize it.
50    *
51    * @param username
52    *          The username provide credentials to the remote peer, or null if there is none.
53    * @param password
54    *          The password provide credentials to the remote peer, or null if there is none.
55    * @param allowedSaslMechanisms
56    *          The possible mechanism(s) to which the client should restrict its mechanism selection to if offered by the
57    *          server, or null/empty if no restriction.
58    * @param handler
59    *          The handler to convey the result of the SASL process to.
60    *          The async result will succeed if the SASL handshake completed successfully, it will fail with
61    *          <ul>
62    *          <li>a {@link MechanismMismatchException} if this client does not support any of the SASL
63    *          mechanisms offered by the server,</li>
64    *          <li>a {@link SaslSystemException} if the SASL handshake fails with either of the
65    *          {@link SaslOutcome#PN_SASL_SYS}, {@link SaslOutcome#PN_SASL_TEMP} or
66    *          {@link SaslOutcome#PN_SASL_PERM} outcomes,</li>
67    *          <li>a {@code javax.security.sasl.AuthenticationException} if the handshake fails with
68    *          the {@link SaslOutcome#PN_SASL_AUTH} outcome or</li>
69    *          <li>a generic {@code javax.security.sasl.SaslException} if the handshake fails due to
70    *          any other reason.</li>
71    *          </ul>
72    */
73   this(string username, string password, Set!string allowedSaslMechanisms ,Handler!ProtonConnection handler) {
74     this.handler = handler;
75     this.username = username;
76     this.password = password;
77     this.mechanismsRestriction = allowedSaslMechanisms;
78   }
79 
80   public void init(NetSocket.Connection socket, ProtonConnection protonConnection, Transport transport) {
81     this.socket = socket;
82     this.connection = protonConnection;
83     this.sasl = transport.sasl();
84     sasl.client();
85   }
86 
87   public void process(Handler!bool completionHandler) {
88     if (sasl is null) {
89       throw new IllegalStateException("Init was not called with the associated transport");
90     }
91 
92     bool done = false;
93     _succeeded = false;
94 
95     try {
96       switch (sasl.getState()) {
97       case SaslState.PN_SASL_IDLE:
98         handleSaslInit();
99         break;
100       case SaslState.PN_SASL_STEP:
101         handleSaslStep();
102         break;
103       case SaslState.PN_SASL_FAIL:
104         handleSaslFail();
105         break;
106       case SaslState.PN_SASL_PASS:
107         done = true;
108         _succeeded = true;
109         version(HUNT_AMQP_DEBUG) logInfo("PN_SASL_PASS !!!");
110         handler.handle(connection);
111         break;
112       default:
113         break;
114       }
115     } catch (Exception e) {
116       done = true;
117       try {
118         if (socket !is null) {
119           socket.close();
120         }
121       } finally {
122       //  handler.handle(Future.failedFuture(e));
123       }
124     }
125 
126     completionHandler.handle(done);
127   }
128 
129   public bool succeeded() {
130     return _succeeded;
131   }
132 
133   private void handleSaslInit() {
134     string[] remoteMechanisms = sasl.getRemoteMechanisms();
135     if (remoteMechanisms !is null && remoteMechanisms.length != 0) {
136       mechanism = ProtonSaslMechanismFinderImpl.findMatchingMechanism( username, password, mechanismsRestriction,
137       remoteMechanisms);
138       if (mechanism !is null) {
139         mechanism.setUsername( username);
140         mechanism.setPassword( password);
141 
142         sasl.setMechanisms( [ mechanism.getName()]);
143         byte[] response = mechanism.getInitialResponse();
144       //  if (response !is null) {
145           sasl.send( response, 0, cast(int)(response.length));
146         //}
147       } else {
148         //  throw new MechanismMismatchException(
149         //      "Could not find a suitable SASL mechanism for the remote peer using the available credentials.",
150         //      remoteMechanisms);
151         //}
152         logError( "Could not find a suitable SASL mechanism for the remote peer using the available credentials.");
153       }
154     }
155   }
156 
157   private void handleSaslStep()  {
158     if (sasl.pending() != 0) {
159       byte[] challenge = new byte[sasl.pending()];
160       sasl.recv(challenge, 0, cast(int)challenge.length);
161       byte[] response = mechanism.getChallengeResponse(challenge);
162       sasl.send(response, 0, cast(int)response.length);
163     }
164   }
165 
166   private void handleSaslFail() {
167 
168     SaslOutcome tmp = sasl.getOutcome();
169     if (tmp == SaslOutcome.PN_SASL_AUTH)
170     {
171       logError("Failed to authenticate");
172     }
173     else if (tmp == SaslOutcome.PN_SASL_SYS || tmp == SaslOutcome.PN_SASL_TEMP)
174     {
175       logError("SASL handshake failed due to a transient error");
176     }
177     else if (tmp == SaslOutcome.PN_SASL_PERM)
178     {
179       logError("SASL handshake failed due to an unrecoverable error");
180     }else
181     {
182       logError("SASL handshake failed");
183     }
184 
185     //switch(sasl.getOutcome()) {
186     //case PN_SASL_AUTH:
187     //    logError("Failed to authenticate");
188     //    break;
189     // // throw new SaslSystemException("Failed to authenticate");
190     //case PN_SASL_SYS:
191     //  goto case;
192     //case PN_SASL_TEMP:
193     //    logError("SASL handshake failed due to a transient error");
194     //    break;
195     // // throw new SaslSystemException(false, "SASL handshake failed due to a transient error");
196     //case PN_SASL_PERM:
197     //     logError("SASL handshake failed due to an unrecoverable error");
198     //     break;
199     // // throw new SaslSystemException(true, "SASL handshake failed due to an unrecoverable error");
200     //default:
201     //      logError("SASL handshake failed");
202     //     break;
203     // // throw new SaslException("SASL handshake failed");
204     //}
205   }
206 }