1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements.  See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership.  The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License.  You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied.  See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  */
19 module hunt.shiro.authc.AbstractAuthenticator;
20 
21 import hunt.shiro.authc.Authenticator;
22 import hunt.shiro.authc.AuthenticationInfo;
23 import hunt.shiro.authc.AuthenticationListener;
24 import hunt.shiro.authc.AuthenticationToken;
25 import hunt.shiro.authc.LogoutAware;
26 
27 import hunt.shiro.Exceptions;
28 import hunt.shiro.subject.PrincipalCollection;
29 
30 import hunt.Exceptions;
31 import hunt.logging.Logger;
32 
33 
34 /**
35  * Superclass for almost all {@link Authenticator} implementations that performs the common work around authentication
36  * attempts.
37  * <p/>
38  * This class delegates the actual authentication attempt to subclasses but supports notification for
39  * successful and failed logins as well as logouts. Notification is sent to one or more registered
40  * {@link AuthenticationListener AuthenticationListener}s to allow for custom processing logic
41  * when these conditions occur.
42  * <p/>
43  * In most cases, the only thing a subclass needs to do (via its {@link #doAuthenticate} implementation)
44  * is perform the actual principal/credential verification process for the submitted {@code AuthenticationToken}.
45  *
46  */
47 abstract class AbstractAuthenticator : Authenticator, LogoutAware {
48 
49     /*-------------------------------------------
50     |             C O N S T A N T S             |
51     ============================================*/
52     /**
53      * Private class log instance.
54      */
55 
56 
57     /*-------------------------------------------
58     |    I N S T A N C E   V A R I A B L E S    |
59     ============================================*/
60     /**
61      * Any registered listeners that wish to know about things during the authentication process.
62      */
63     private AuthenticationListener[] listeners;
64 
65     /*-------------------------------------------
66     |         C O N S T R U C T O R S           |
67     ============================================*/
68 
69     /**
70      * Default no-argument constructor. Ensures the internal
71      * {@link AuthenticationListener AuthenticationListener} collection is a non-null {@code ArrayList}.
72      */
73     this() {
74     }
75 
76     /*--------------------------------------------
77     |  A C C E S S O R S / M O D I F I E R S    |
78     ============================================*/
79 
80     /**
81      * Sets the {@link AuthenticationListener AuthenticationListener}s that should be notified during authentication
82      * attempts.
83      *
84      * @param listeners one or more {@code AuthenticationListener}s that should be notified due to an
85      *                  authentication attempt.
86      */
87     //@SuppressWarnings({"UnusedDeclaration"})
88     void setAuthenticationListeners(AuthenticationListener[] listeners) {
89         this.listeners = listeners;
90     }
91 
92     /**
93      * Returns the {@link AuthenticationListener AuthenticationListener}s that should be notified during authentication
94      * attempts.
95      *
96      * @return the {@link AuthenticationListener AuthenticationListener}s that should be notified during authentication
97      *         attempts.
98      */
99     //@SuppressWarnings({"UnusedDeclaration"})
100     AuthenticationListener[] getAuthenticationListeners() {
101         return this.listeners;
102     }
103 
104     /*-------------------------------------------
105     |               M E T H O D S               |
106     ============================================*/
107 
108     /**
109      * Notifies any registered {@link AuthenticationListener AuthenticationListener}s that
110      * authentication was successful for the specified {@code token} which resulted in the specified
111      * {@code info}.  This implementation merely iterates over the internal {@code listeners} collection and
112      * calls {@link AuthenticationListener#onSuccess(AuthenticationToken, AuthenticationInfo) onSuccess}
113      * for each.
114      *
115      * @param token the submitted {@code AuthenticationToken} that resulted in a successful authentication.
116      * @param info  the returned {@code AuthenticationInfo} resulting from the successful authentication.
117      */
118     protected void notifySuccess(AuthenticationToken token, AuthenticationInfo info) {
119         foreach(AuthenticationListener listener ; this.listeners) {
120             if(listener !is null)
121                 listener.onSuccess(token, info);
122         }
123     }
124 
125     /**
126      * Notifies any registered {@link AuthenticationListener AuthenticationListener}s that
127      * authentication failed for the
128      * specified {@code token} which resulted in the specified {@code ae} exception.  This implementation merely
129      * iterates over the internal {@code listeners} collection and calls
130      * {@link AuthenticationListener#onFailure(AuthenticationToken, AuthenticationException) onFailure}
131      * for each.
132      *
133      * @param token the submitted {@code AuthenticationToken} that resulted in a failed authentication.
134      * @param ae    the resulting {@code AuthenticationException} that caused the authentication to fail.
135      */
136     protected void notifyFailure(AuthenticationToken token, AuthenticationException ae) {
137         foreach(AuthenticationListener listener ; this.listeners) {
138             if(listener !is null)
139                 listener.onFailure(token, ae);
140         }
141     }
142 
143     /**
144      * Notifies any registered {@link AuthenticationListener AuthenticationListener}s that a
145      * {@code Subject} has logged-out.  This implementation merely
146      * iterates over the internal {@code listeners} collection and calls
147      * {@link AuthenticationListener#onLogout(hunt.shiro.subject.PrincipalCollection) onLogout}
148      * for each.
149      *
150      * @param principals the identifying principals of the {@code Subject}/account logging out.
151      */
152     protected void notifyLogout(PrincipalCollection principals) {
153         foreach(AuthenticationListener listener ; this.listeners) {
154             if(listener !is null)
155                 listener.onLogout(principals);
156         }
157     }
158 
159     /**
160      * This implementation merely calls
161      * {@link #notifyLogout(hunt.shiro.subject.PrincipalCollection) notifyLogout} to allow any registered listeners
162      * to react to the logout.
163      *
164      * @param principals the identifying principals of the {@code Subject}/account logging out.
165      */
166      void onLogout(PrincipalCollection principals) {
167         notifyLogout(principals);
168     }
169 
170     /**
171      * Implementation of the {@link Authenticator} interface that functions in the following manner:
172      * <ol>
173      * <li>Calls template {@link #doAuthenticate doAuthenticate} method for subclass execution of the actual
174      * authentication behavior.</li>
175      * <li>If an {@code AuthenticationException} is thrown during {@code doAuthenticate},
176      * {@link #notifyFailure(AuthenticationToken, AuthenticationException) notify} any registered
177      * {@link AuthenticationListener AuthenticationListener}s of the exception and then propagate the exception
178      * for the caller to handle.</li>
179      * <li>If no exception is thrown (indicating a successful login),
180      * {@link #notifySuccess(AuthenticationToken, AuthenticationInfo) notify} any registered
181      * {@link AuthenticationListener AuthenticationListener}s of the successful attempt.</li>
182      * <li>Return the {@code AuthenticationInfo}</li>
183      * </ol>
184      *
185      * @param token the submitted token representing the subject's (user's) login principals and credentials.
186      * @return the AuthenticationInfo referencing the authenticated user's account data.
187      * @throws AuthenticationException if there is any problem during the authentication process - see the
188      *                                 interface's JavaDoc for a more detailed explanation.
189      */
190      final AuthenticationInfo authenticate(AuthenticationToken token){
191 
192         if (token is null) {
193             throw new IllegalArgumentException("Method argument (authentication token) cannot be null.");
194         }
195 
196         version(HUNT_SHIRO_DEBUG) tracef("Authentication attempt received for token [%s]", token);
197 
198         AuthenticationInfo info;
199         try {
200             info = doAuthenticate(token);
201             if (info is null) {
202                 string msg = "No account information found for authentication token [" ~ 
203                         (cast(Object)token).toString() ~ "] by this " ~
204                         "Authenticator instance.  Please check that it is configured correctly.";
205                 throw new AuthenticationException(msg);
206             }
207         } catch (Throwable t) {
208             warning(t.msg);
209             version(HUNT_DEBUG) warning(t);
210             AuthenticationException ae = null;
211             auto tCast = cast(AuthenticationException)t;
212             if (tCast !is null) {
213                 ae = tCast;
214             }
215             if (ae is null) {
216                 //Exception thrown was not an expected AuthenticationException.  Therefore it is probably a little more
217                 //severe or unexpected.  So, wrap in an AuthenticationException, log to warn, and propagate:
218                 string msg = "Authentication failed for token submission [" ~ 
219                         (cast(Object)token).toString() ~ "].  Possible unexpected " ~
220                         "error? (Typical or expected login exceptions should extend from AuthenticationException).";
221                 ae = new AuthenticationException(msg, t);
222             }
223             try {
224                 notifyFailure(token, ae);
225             } catch (Throwable t2) {
226                 version(HUNT_DEBUG) {
227                     string msg = "Unable to send notification for failed authentication attempt - listener error?.  " ~
228                             "Please check your AuthenticationListener implementation(s).  Logging sending exception " ~
229                             "and propagating original AuthenticationException instead...";
230                     warning(msg, t2);
231                 }
232             }
233 
234 
235             throw ae;
236         }
237 
238         version(HUNT_SHIRO_DEBUG) {
239             infof("Authentication successful for token [%s].  Returned account [%s]", 
240                 token, info);
241         }
242 
243         notifySuccess(token, info);
244 
245         return info;
246     }
247 
248     /**
249      * Template design pattern hook for subclasses to implement specific authentication behavior.
250      * <p/>
251      * Common behavior for most authentication attempts is encapsulated in the
252      * {@link #authenticate} method and that method invokes this one for custom behavior.
253      * <p/>
254      * <b>N.B.</b> Subclasses <em>should</em> throw some kind of
255      * {@code AuthenticationException} if there is a problem during
256      * authentication instead of returning {@code null}.  A {@code null} return value indicates
257      * a configuration or programming error, since {@code AuthenticationException}s should
258      * indicate any expected problem (such as an unknown account or username, or invalid password, etc).
259      *
260      * @param token the authentication token encapsulating the user's login information.
261      * @return an {@code AuthenticationInfo} object encapsulating the user's account information
262      *         important to Shiro.
263      * @throws AuthenticationException if there is a problem logging in the user.
264      */
265     protected abstract AuthenticationInfo doAuthenticate(AuthenticationToken token);
266 
267 
268 }