View Javadoc
1   package fr.ifremer.quadrige2.ui.swing.common.content.login;
2   
3   /*-
4    * #%L
5    * Quadrige2 Core :: Quadrige2 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.quadrige2.core.exception.Quadrige2TechnicalException;
29  import fr.ifremer.quadrige2.core.security.AuthenticationInfo;
30  import fr.ifremer.quadrige2.core.security.SecurityContextHelper;
31  import fr.ifremer.quadrige2.core.security.remote.AuthenticationRemoteException;
32  import fr.ifremer.quadrige2.core.security.remote.AuthenticationRemoteService;
33  import fr.ifremer.quadrige2.core.service.ClientServiceLocator;
34  import fr.ifremer.quadrige2.core.service.administration.user.UserService;
35  import fr.ifremer.quadrige2.core.vo.administration.user.QuserVO;
36  import fr.ifremer.quadrige2.synchro.service.client.SynchroRestClientService;
37  import fr.ifremer.quadrige2.ui.swing.common.action.AbstractMainUIAction;
38  import fr.ifremer.quadrige2.ui.swing.common.action.CloseApplicationAction;
39  import fr.ifremer.quadrige2.ui.swing.common.content.AbstractMainUIHandler;
40  import org.apache.commons.lang3.StringUtils;
41  import org.apache.commons.lang3.builder.ToStringBuilder;
42  import org.apache.commons.lang3.builder.ToStringStyle;
43  import org.apache.commons.logging.Log;
44  import org.apache.commons.logging.LogFactory;
45  import org.nuiton.jaxx.application.swing.action.AbstractApplicationAction;
46  
47  import javax.swing.SwingUtilities;
48  
49  import static org.nuiton.i18n.I18n.t;
50  
51  /**
52   * Manage authentification. This Action is called directly or when a security error occur.
53   * After an authentification, a callback action could be called
54   *
55   * @author Ludovic Pecquot <ludovic.pecquot@e-is.pro>
56   */
57  public class AuthenticationAction extends AbstractMainUIAction {
58  
59      private static final Log LOG = LogFactory.getLog(AuthenticationAction.class);
60  
61  
62      // TODO : use JXLoginPane to ask credentials
63  //    JXLoginPane loginPane = new JXLoginPane(new LoginService() {
64  //        @Override
65  //        public boolean authenticate(String name, char[] password, String server) throws Exception {
66  //            return false;
67  //        }
68  //    },
69  //            null, new DefaultUserNameStore());
70  
71  
72      private AbstractApplicationAction callbackAction;
73      private boolean authenticated;
74      private boolean authenticationCanceled;
75  
76      /**
77       * <p>Constructor for AuthenticationAction.</p>
78       *
79       * @param handler a {@link AbstractMainUIHandler} object.
80       */
81      public AuthenticationAction(AbstractMainUIHandler handler) {
82          this(handler, null);
83      }
84  
85      /**
86       * <p>Constructor for AuthenticationAction.</p>
87       *
88       * @param handler a {@link AbstractMainUIHandler} object.
89       * @param callbackAction a {@link AbstractApplicationAction} object.
90       */
91      public AuthenticationAction(AbstractMainUIHandler handler, AbstractApplicationAction callbackAction) {
92          super(handler, true);
93          this.callbackAction = callbackAction;
94          setActionDescription(t("quadrige2.action.authenticate.title"));
95      }
96  
97      /** {@inheritDoc} */
98      @Override
99      public void doAction() throws Exception {
100 
101         // init
102         authenticated = false;
103         authenticationCanceled = false;
104         UserService userService = ClientServiceLocator.instance().getUserService();
105 
106         // action message
107         String message = null;
108         if (callbackAction != null) {
109             message = callbackAction.getActionDescription();
110         }
111 
112         while (!authenticated) {
113 
114             // ask authentication information
115             AuthenticationInfo authenticationInfo = getHandler().askAuthenticationInfo(message);
116 
117             if (authenticationInfo == null
118                 || StringUtils.isBlank(authenticationInfo.getLogin())) {
119 
120                 if (LOG.isDebugEnabled()) {
121                     LOG.debug("insufficient authentication information, cancelling action");
122                 }
123                 callbackAction = null;
124                 authenticationCanceled = true;
125 
126                 if (getConfig().isAuthenticationForced() && !getContext().isAuthenticated()) {
127                     // if authentication is forced, the application can't continue
128                     getContext().getErrorHelper().showErrorDialog(t("quadrige2.action.authenticate.forced.message", getConfig().getApplicationName()));
129                     getActionEngine().runInternalAction(handler, CloseApplicationAction.class);
130                 }
131 
132                 return;
133             }
134 
135             String login = authenticationInfo.getLogin();
136             boolean isLocalUserWithEmptyPassword = userService.isLocalUserWithNoPassword(login);
137 
138             try {
139 
140                 // If local user with NO password : authenticated using login only
141                 if (isLocalUserWithEmptyPassword) {
142 
143                     // Check password IS empty
144                     if (StringUtils.isNotEmpty(authenticationInfo.getPassword())) {
145                         getContext().getErrorHelper().showErrorDialog(t("quadrige2.error.authenticate.badCredential"));
146 
147                         // don't loop if authentication is not forced
148                         if (!getConfig().isAuthenticationForced()) {
149                             return;
150                         }
151                     }
152                     else {
153                         authenticated = SecurityContextHelper.authenticate(login, null);
154                     }
155                 }
156 
157                 else {
158                     // Check password is NOT blank
159                     if (StringUtils.isBlank(authenticationInfo.getPassword())) {
160                         throw new AuthenticationRemoteException(t("quadrige2.error.authenticate.badCredential"));
161                     }
162 
163                     boolean hasPasswordInDatabase;
164                     if (getConfig().isAuthenticationDisabled()) {
165 
166                         // password always provided, don't connect to authentication site
167                         hasPasswordInDatabase = true;
168                     } else {
169 
170                         // check if user has a stored password
171                         hasPasswordInDatabase = userService.hasPassword(login);
172                     }
173 
174                     // First, try to connect using password in local DB
175                     if (hasPasswordInDatabase) {
176 
177                         // security authentication
178                         authenticated = SecurityContextHelper.authenticate(login, authenticationInfo.getPassword());
179 
180                     }
181 
182                     // If authentication on local password failed, or not possible
183                     if (!authenticated) {
184 
185                         // Try to auth against remote sites
186                         AuthenticationRemoteService authRemoteService = ClientServiceLocator.instance().getAuthenticationRemoteService();
187                         boolean remoteAuthenticated = authRemoteService.canAuthenticate(authenticationInfo);
188 
189                         if (remoteAuthenticated) {
190 
191                             // delete password in database because it can be updated
192                             userService.resetPassword(login);
193                             hasPasswordInDatabase = false;
194 
195                             // authenticate with empty password to force authentication process, it will be updated only if the person is in the database
196                             authenticated = SecurityContextHelper.authenticate(login, null);
197 
198                             // Try to update person from server, then retry
199                             if (!authenticated) {
200 
201                                 if (tryUpdatePersonFromServer(authenticationInfo)) {
202                                     // new attempt to delete password because login can change after tryUpdatePersonFromServer
203                                     userService.resetPassword(login);
204                                     authenticated = SecurityContextHelper.authenticate(login, null);
205                                 } else {
206                                     throw new AuthenticationRemoteException(t("quadrige2.error.authenticate.badCredential"));
207                                 }
208                             }
209                         }
210                     }
211 
212                     if (!authenticated) {
213                         throw new AuthenticationRemoteException(t("quadrige2.error.authenticate.notFound", login));
214                     }
215 
216                     if (LOG.isDebugEnabled()) {
217                         LOG.debug("user '" + login + "' authenticated");
218                     }
219 
220                     if (!hasPasswordInDatabase) {
221                         // update user password in local database
222                         userService.updatePasswordByUserId(
223                                 SecurityContextHelper.getQuadrige2UserId(),
224                                 authenticationInfo.getPassword());
225                     }
226                 }
227             } catch (AuthenticationRemoteException ae) {
228 
229                 getContext().getErrorHelper().showErrorDialog(ae.getLocalizedMessage());
230 
231                 // don't loop if authentication is not forced
232                 if (!getConfig().isAuthenticationForced()) {
233                     return;
234                 }
235             }
236         }
237     }
238 
239     /** {@inheritDoc} */
240     @Override
241     public void postSuccessAction() {
242         super.postSuccessAction();
243 
244         // don't go further if user hits 'cancel'
245         if (authenticationCanceled) {
246             return;
247         }
248 
249         getContext().setAuthenticated(authenticated);
250 
251         // re-attempt the action
252         if (authenticated && callbackAction != null) {
253             if (LOG.isDebugEnabled()) {
254                 LOG.debug("run callback action " + callbackAction.getClass().getSimpleName());
255             }
256             SwingUtilities.invokeLater(new Runnable() {
257 
258                 @Override
259                 public void run() {
260                     getActionEngine().runAction(callbackAction);
261                 }
262             });
263         }
264 
265     }
266 
267     /** {@inheritDoc} */
268     @Override
269     public void postFailedAction(Throwable error) {
270         super.postFailedAction(error);
271 
272         getContext().setAuthenticated(false);
273 
274     }
275 
276     /* -- internal methods -- */
277 
278     /**
279      * <p>tryUpdatePersonFromServer.</p>
280      *
281      * @param authenticationInfo a {@link fr.ifremer.quadrige2.core.security.AuthenticationInfo} object.
282      * @return a boolean.
283      */
284     protected boolean tryUpdatePersonFromServer(AuthenticationInfo authenticationInfo) {
285         if (!getConfig().isSynchronizationEnabled()) {
286             return false;
287         }
288 
289         // Get the user from the synchro server
290         SynchroRestClientService synchroRestClientService = ClientServiceLocator.instance().getSynchroRestClientService();
291         QuserVO user;
292         try {
293             user = synchroRestClientService.getUser(authenticationInfo);
294         } catch (Quadrige2TechnicalException e) {
295             return false;
296         }
297 
298         if (user == null) {
299             return false;
300         }
301 
302         // Make sure the person given by the server is the good one
303         if (!Objects.equal(user.getQuserIntranetLg(), authenticationInfo.getLogin())
304             && !Objects.equal(user.getQuserExtranetLg(), authenticationInfo.getLogin())) {
305             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.",
306                     authenticationInfo.getLogin(),
307                     user.getQuserIntranetLg(),
308                     user.getQuserExtranetLg()));
309             return false;
310         }
311 
312         if (LOG.isDebugEnabled()) {
313             LOG.debug("Will save person, retrieve from synchro server: " + ToStringBuilder.reflectionToString(user, ToStringStyle.SHORT_PREFIX_STYLE));
314         }
315         UserService personService = ClientServiceLocator.instance().getUserService();
316         personService.save(user, true);
317 
318         return true;
319     }
320 
321 }