View Javadoc
1   package fr.ifremer.quadrige3.ui.swing.content.login;
2   
3   /*-
4    * #%L
5    * Quadrige3 Core :: Quadrige3 UI Common
6    * $Id:$
7    * $HeadURL:$
8    * %%
9    * Copyright (C) 2017 Ifremer
10   * %%
11   * This program is free software: you can redistribute it and/or modify
12   * it under the terms of the GNU Affero General Public License as published by
13   * the Free Software Foundation, either version 3 of the License, or
14   * (at your option) any later version.
15   *
16   * This program is distributed in the hope that it will be useful,
17   * but WITHOUT ANY WARRANTY; without even the implied warranty of
18   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19   * GNU General Public License for more details.
20   *
21   * You should have received a copy of the GNU Affero General Public License
22   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
23   * #L%
24   */
25  
26  
27  import com.google.common.base.Objects;
28  import fr.ifremer.quadrige3.core.exception.QuadrigeTechnicalException;
29  import fr.ifremer.quadrige3.core.security.AuthenticationInfo;
30  import fr.ifremer.quadrige3.core.security.SecurityContextHelper;
31  import fr.ifremer.quadrige3.core.security.remote.AuthenticationRemoteException;
32  import fr.ifremer.quadrige3.core.security.remote.AuthenticationRemoteService;
33  import fr.ifremer.quadrige3.core.service.ClientServiceLocator;
34  import fr.ifremer.quadrige3.core.service.administration.user.UserService;
35  import fr.ifremer.quadrige3.core.vo.administration.user.QuserVO;
36  import fr.ifremer.quadrige3.core.vo.system.generalCondition.GeneralConditionVO;
37  import fr.ifremer.quadrige3.synchro.service.client.SynchroRestClientService;
38  import fr.ifremer.quadrige3.ui.swing.DialogHelper;
39  import fr.ifremer.quadrige3.ui.swing.action.AbstractReloadCurrentScreenAction;
40  import fr.ifremer.quadrige3.ui.swing.action.CloseApplicationAction;
41  import fr.ifremer.quadrige3.ui.swing.content.AbstractMainUIHandler;
42  import org.apache.commons.lang3.StringUtils;
43  import org.apache.commons.lang3.builder.ToStringBuilder;
44  import org.apache.commons.lang3.builder.ToStringStyle;
45  import org.apache.commons.logging.Log;
46  import org.apache.commons.logging.LogFactory;
47  import org.nuiton.jaxx.application.swing.action.AbstractApplicationAction;
48  
49  import javax.swing.*;
50  
51  import static org.nuiton.i18n.I18n.t;
52  
53  /**
54   * Manage authentification. This Action is called directly or when a security error occur.
55   * After an authentification, a callback action could be called
56   *
57   * @author Ludovic Pecquot <ludovic.pecquot@e-is.pro>
58   */
59  public class AuthenticationAction extends AbstractReloadCurrentScreenAction {
60  
61      private static final Log LOG = LogFactory.getLog(AuthenticationAction.class);
62  
63  
64      // TODO : use JXLoginPane to ask credentials
65  //    JXLoginPane loginPane = new JXLoginPane(new LoginService() {
66  //        @Override
67  //        public boolean authenticate(String name, char[] password, String server) throws Exception {
68  //            return false;
69  //        }
70  //    },
71  //            null, new DefaultUserNameStore());
72  
73  
74      private AbstractApplicationAction callbackAction;
75      private boolean authenticated;
76      private boolean authenticationCanceled;
77  
78      /**
79       * <p>Constructor for AuthenticationAction.</p>
80       *
81       * @param handler a {@link AbstractMainUIHandler} object.
82       */
83      public AuthenticationAction(AbstractMainUIHandler handler) {
84          this(handler, null);
85      }
86  
87      /**
88       * <p>Constructor for AuthenticationAction.</p>
89       *
90       * @param handler a {@link AbstractMainUIHandler} object.
91       * @param callbackAction a {@link AbstractApplicationAction} object.
92       */
93      public AuthenticationAction(AbstractMainUIHandler handler, AbstractApplicationAction callbackAction) {
94          super(handler, true);
95          this.callbackAction = callbackAction;
96          setActionDescription(t("quadrige3.action.authenticate.title"));
97      }
98  
99      /** {@inheritDoc} */
100     @Override
101     public void doActionBeforeReload() {
102 
103         // init
104         authenticated = false;
105         authenticationCanceled = false;
106         UserService userService = ClientServiceLocator.instance().getUserService();
107 
108         // action message
109         String message = null;
110         if (callbackAction != null) {
111             message = callbackAction.getActionDescription();
112         }
113 
114         while (!authenticated) {
115 
116             // ask authentication information
117             AuthenticationInfo authenticationInfo = getHandler().askAuthenticationInfo(message);
118 
119             if (authenticationInfo == null
120                 || StringUtils.isBlank(authenticationInfo.getLogin())) {
121 
122                 if (LOG.isDebugEnabled()) {
123                     LOG.debug("insufficient authentication information, cancelling action");
124                 }
125                 callbackAction = null;
126                 authenticationCanceled = true;
127                 setSkipScreenReload(true);
128 
129                 if (getConfig().isAuthenticationForced() && !getContext().isAuthenticated()) {
130                     // if authentication is forced, the application can't continue
131                     getContext().getErrorHelper().showErrorDialog(t("quadrige3.action.authenticate.forced.message", getConfig().getApplicationName()));
132                     getActionEngine().runInternalAction(handler, CloseApplicationAction.class);
133                 }
134 
135                 return;
136             }
137 
138             String login = authenticationInfo.getLogin();
139             boolean isLocalUserWithEmptyPassword = userService.isLocalUserWithNoPassword(login);
140 
141             try {
142 
143                 // If local user with NO password : authenticated using login only
144                 if (isLocalUserWithEmptyPassword) {
145 
146                     // Check password IS empty
147                     if (StringUtils.isNotEmpty(authenticationInfo.getPassword())) {
148                         getContext().getErrorHelper().showErrorDialog(t("quadrige3.error.authenticate.badCredential"));
149 
150                         // don't loop if authentication is not forced
151                         if (!getConfig().isAuthenticationForced()) {
152                             return;
153                         }
154                     }
155                     else {
156                         authenticated = SecurityContextHelper.authenticate(login, null);
157                     }
158                 }
159 
160                 else {
161                     // Check password is NOT blank
162                     if (StringUtils.isBlank(authenticationInfo.getPassword())) {
163                         throw new AuthenticationRemoteException(t("quadrige3.error.authenticate.badCredential"));
164                     }
165 
166                     boolean hasPasswordInDatabase;
167                     if (getConfig().isAuthenticationDisabled()) {
168 
169                         // password always provided, don't connect to authentication site
170                         hasPasswordInDatabase = true;
171                     } else {
172 
173                         // check if user has a stored password
174                         hasPasswordInDatabase = userService.hasPassword(login);
175                     }
176 
177                     // First, try to connect using password in local DB
178                     if (hasPasswordInDatabase) {
179 
180                         // security authentication
181                         authenticated = SecurityContextHelper.authenticate(login, authenticationInfo.getPassword());
182 
183                     }
184 
185                     // If authentication on local password failed, or not possible
186                     if (!authenticated) {
187 
188                         // Try to auth against remote sites
189                         AuthenticationRemoteService authRemoteService = ClientServiceLocator.instance().getAuthenticationRemoteService();
190                         boolean remoteAuthenticated = authRemoteService.canAuthenticate(authenticationInfo);
191 
192                         if (remoteAuthenticated) {
193 
194                             // delete password in database because it can be updated
195                             userService.resetPassword(login);
196                             hasPasswordInDatabase = false;
197 
198                             // authenticate with empty password to force authentication process, it will be updated only if the person is in the database
199                             authenticated = SecurityContextHelper.authenticate(login, null);
200 
201                             // Try to update person from server, then retry
202                             if (!authenticated) {
203 
204                                 if (tryUpdatePersonFromServer(authenticationInfo)) {
205                                     // new attempt to delete password because login can change after tryUpdatePersonFromServer
206                                     userService.resetPassword(login);
207                                     authenticated = SecurityContextHelper.authenticate(login, null);
208                                 } else {
209                                     throw new AuthenticationRemoteException(t("quadrige3.error.authenticate.badCredential"));
210                                 }
211                             }
212                         }
213                     }
214 
215                     if (!authenticated) {
216                         throw new AuthenticationRemoteException(t("quadrige3.error.authenticate.notFound", login));
217                     }
218 
219                     if (LOG.isDebugEnabled()) {
220                         LOG.debug("user '" + login + "' authenticated");
221                     }
222 
223                     if (!hasPasswordInDatabase) {
224                         // update user password in local database
225                         userService.updatePasswordByUserId(
226                                 SecurityContextHelper.getQuadrigeUserId(),
227                                 authenticationInfo.getPassword());
228                     }
229                 }
230             } catch (AuthenticationRemoteException ae) {
231 
232                 getContext().getErrorHelper().showErrorDialog(ae.getLocalizedMessage());
233 
234                 // don't loop if authentication is not forced
235                 if (!getConfig().isAuthenticationForced()) {
236                     return;
237                 }
238             }
239         }
240 
241         // set authentication result to context
242         getContext().setAuthenticated(authenticated);
243 
244         if (authenticated) {
245             // if authenticated, save login to config
246             getConfig().setAuthenticationDefaultUsername(getContext().getAuthenticationInfo().getLogin());
247 
248             // Check if general condition is accepted or not
249             SynchroRestClientService synchroRestClientService = ClientServiceLocator.instance().getSynchroRestClientService();
250             GeneralConditionVO condition = null;
251             try {
252                 condition = synchroRestClientService.getLastNonAcceptedGeneralCondition(getContext().getAuthenticationInfo());
253             } catch (QuadrigeTechnicalException e) {
254                 // The server is offline, no problem
255             }
256             if (condition != null) {
257                 // Show general condition
258                 int result = getContext().getDialogHelper().showOptionDialog(
259                     getContext().getMainUI(),
260                     null,
261                     condition.getContent(),
262                     t("quadrige3.action.authenticate.generalCondition.confirm"),
263                     t("quadrige3.action.authenticate.generalCondition.title"),
264                     DialogHelper.QUESTION_MESSAGE_WITH_LARGE_TEXT,
265                     JOptionPane.YES_NO_OPTION
266                 );
267 
268                 // If accepted ok, if not, quit
269                 if (result == JOptionPane.YES_OPTION) {
270                     synchroRestClientService.acceptGeneralCondition(getContext().getAuthenticationInfo(), condition.getId());
271                 } else {
272                     getActionEngine().runInternalAction(handler, CloseApplicationAction.class);
273                 }
274             }
275         }
276 
277     }
278 
279     /** {@inheritDoc} */
280     @Override
281     public void postSuccessAction() {
282         super.postSuccessAction();
283 
284         // don't go further if user hits 'cancel'
285         if (authenticationCanceled) {
286             return;
287         }
288 
289         // re-attempt the action
290         if (authenticated && callbackAction != null) {
291             if (LOG.isDebugEnabled()) {
292                 LOG.debug("run callback action " + callbackAction.getClass().getSimpleName());
293             }
294             SwingUtilities.invokeLater(() -> getActionEngine().runAction(callbackAction));
295         }
296 
297     }
298 
299     /** {@inheritDoc} */
300     @Override
301     public void postFailedAction(Throwable error) {
302         super.postFailedAction(error);
303 
304         getContext().setAuthenticated(false);
305 
306     }
307 
308     /* -- internal methods -- */
309 
310     /**
311      * <p>tryUpdatePersonFromServer.</p>
312      *
313      * @param authenticationInfo a {@link AuthenticationInfo} object.
314      * @return a boolean.
315      */
316     protected boolean tryUpdatePersonFromServer(AuthenticationInfo authenticationInfo) {
317         if (!getConfig().isSynchronizationEnabled()) {
318             return false;
319         }
320 
321         // Get the user from the synchro server
322         QuserVO user;
323         try {
324             SynchroRestClientService synchroRestClientService = ClientServiceLocator.instance().getSynchroRestClientService();
325             user = synchroRestClientService.getUser(authenticationInfo);
326         } catch (QuadrigeTechnicalException e) {
327             return false;
328         }
329 
330         if (user == null) {
331             return false;
332         }
333 
334         // Make sure the person given by the server is the good one
335         if (!Objects.equal(user.getQuserIntranetLg(), authenticationInfo.getLogin())
336             && !Objects.equal(user.getQuserExtranetLg(), authenticationInfo.getLogin())) {
337             LOG.warn(String.format("Synchronization server send a bad person, with wrong login: expected [%s] but found [username=%s] and [usernameExtranet=%s]. Check configuration on synchro server.",
338                     authenticationInfo.getLogin(),
339                     user.getQuserIntranetLg(),
340                     user.getQuserExtranetLg()));
341             return false;
342         }
343 
344         if (LOG.isDebugEnabled()) {
345             LOG.debug("Will save person, retrieve from synchro server: " + ToStringBuilder.reflectionToString(user, ToStringStyle.SHORT_PREFIX_STYLE));
346         }
347         UserService personService = ClientServiceLocator.instance().getUserService();
348         personService.save(user, true);
349 
350         return true;
351     }
352 
353 }