Statistics
| Revision:

svn-gvsig-desktop / trunk / org.gvsig.desktop / org.gvsig.desktop.plugin / org.gvsig.downloader / org.gvsig.downloader.swing / org.gvsig.downloader.swing.scribejava / src / main / java / org / gvsig / downloader / swing / scribejava / keycloak / DownloaderAuthenticationKeycloakRequester.java @ 47828

History | View | Annotate | Download (15.7 KB)

1
/*
2
 * gvSIG. Desktop Geographic Information System.
3
 * 
4
 * Copyright (C) 2007-2020 gvSIG Association.
5
 * 
6
 * This program is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU General Public License
8
 * as published by the Free Software Foundation; either version 3
9
 * of the License, or (at your option) any later version.
10
 * 
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 * 
16
 * You should have received a copy of the GNU General Public License 
17
 * along with this program. If not, see <https://www.gnu.org/licenses/>. 
18
 * 
19
 * For any additional information, do not hesitate to contact us
20
 * at info AT gvsig.com, or visit our website www.gvsig.com.
21
 */
22
package org.gvsig.downloader.swing.scribejava.keycloak;
23

    
24
import com.github.scribejava.core.builder.ServiceBuilder;
25
import com.github.scribejava.core.model.OAuth2AccessToken;
26
import com.github.scribejava.core.model.OAuth2AccessTokenErrorResponse;
27
import com.github.scribejava.core.model.OAuthRequest;
28
import com.github.scribejava.core.model.Response;
29
import com.github.scribejava.core.model.Verb;
30
import com.github.scribejava.core.oauth.OAuth20Service;
31
import java.awt.Frame;
32
import java.awt.Toolkit;
33
import java.io.IOException;
34
import java.net.URI;
35
import java.net.URISyntaxException;
36
import java.util.UUID;
37
import java.util.concurrent.ExecutionException;
38
import java.util.concurrent.Executor;
39
import javax.json.JsonObject;
40
import javax.swing.JOptionPane;
41
import javax.swing.SwingUtilities;
42
import org.apache.commons.codec.net.URLCodec;
43
import org.apache.commons.lang.StringUtils;
44
import org.apache.commons.lang3.mutable.MutableBoolean;
45
import org.gvsig.desktopopen.DesktopOpen;
46
import org.gvsig.downloader.DownloaderAuthenticationRequester;
47
import org.gvsig.downloader.DownloaderLocator;
48
import org.gvsig.downloader.DownloaderManager;
49
import org.gvsig.downloader.spi.AbstractDownloaderAuthenticationRequester;
50
import org.gvsig.downloader.swing.scribejava.keycloak.callbacks.CallbackAuthorizationHandler;
51
import org.gvsig.downloader.swing.scribejava.keycloak.callbacks.CallbackLogoutHandler;
52
import org.gvsig.json.Json;
53
import org.gvsig.tools.ToolsLocator;
54
import org.gvsig.tools.i18n.I18nManager;
55
import org.gvsig.tools.swing.api.ToolsSwingLocator;
56
import org.gvsig.tools.swing.api.threadsafedialogs.ThreadSafeDialogsManager;
57
import org.gvsig.tools.util.ToolsUtilLocator;
58
import org.slf4j.Logger;
59
import org.slf4j.LoggerFactory;
60

    
61
/**
62
 *
63
 * @author gvSIG Team
64
 */
65
@SuppressWarnings("UseSpecificCatch")
66
public class DownloaderAuthenticationKeycloakRequester
67
        extends AbstractDownloaderAuthenticationRequester
68
        implements DownloaderAuthenticationRequester {
69

    
70
    private static final Logger LOGGER = LoggerFactory.getLogger(DownloaderAuthenticationKeycloakRequester.class);
71

    
72
    private final Object monitor = new Object();
73
    private boolean stopedWaitingForResponse = false;
74
    private boolean userCancelledWaitingForResponse = false;
75

    
76
    private DownloaderKeycloakCredentials credentials = null;
77
    
78
    public DownloaderAuthenticationKeycloakRequester(DownloaderAuthenticationKeycloakConfig config) {
79
        super(config);
80
        this.credentials = null;
81
    }
82

    
83
    @Override
84
    public DownloaderAuthenticationKeycloakConfig getConfig() {
85
        return (DownloaderAuthenticationKeycloakConfig) this.config;
86
    }
87

    
88
    @Override
89
    public boolean requestAuthorization(Executor executorUI) {
90
        try {
91
            String requestId = StringUtils.remove(UUID.randomUUID().toString(), "-");
92
            String authCallback = "http://localhost:" + this.getConfig().getLocalPort() + "/auth_" + requestId;
93

    
94
            OAuth20Service service = createService(authCallback);
95
            
96
            DownloaderKeycloakCredentials theCredentials =  this.retrieveCredentials();
97
            if( theCredentials != null ) {
98
                if( theCredentials.isAccessTokenExpired() ) {
99
                    if( !theCredentials.isRefreshTokenExpired() ) {
100
                        OAuth2AccessToken token = theCredentials.getToken();
101
                        try {
102
                            token = service.refreshAccessToken(token.getRefreshToken());
103
                            if( token!=null ) {
104
                                System.out.println("ACCESS TOKEN UPDATED");
105
                                this.setCredentials(
106
                                        new DownloaderKeycloakCredentials(
107
                                                this.config,
108
                                                token, 
109
                                                theCredentials.getUserid(), 
110
                                                System.currentTimeMillis()
111
                                        )
112
                                );
113
                                return true;
114
                            }
115
                        } catch(OAuth2AccessTokenErrorResponse ex) {
116
                            LOGGER.debug("Refresh access token failed", ex);
117
                        }
118
                    }
119
                    // Si llega aqui asumo que el token de refresco a caducado,
120
                    // lo borro y intento una autenticacion normal.
121
                    this.removeCredetials();
122
                } else {
123
                    this.setCredentials(theCredentials);
124
                    return true;
125
                }
126
            }
127
            return this.requestIdentification2(requestId, authCallback, service);
128
        } catch (Exception ex) {
129
            LOGGER.warn("Can't request authorization", ex);
130
            return false;
131
        }
132
    }
133

    
134
    private boolean requestIdentification2(String requestId, String authCallback, OAuth20Service service) {
135
        try {
136
            if (!SwingUtilities.isEventDispatchThread()) {
137
                try {
138
                    MutableBoolean r = new MutableBoolean();
139
                    SwingUtilities.invokeAndWait(() -> {
140
                        r.setValue(requestIdentification2(requestId, authCallback, service));
141
                    });
142
                    return r.booleanValue();
143
                } catch (Exception ex) {
144
                    return false;
145
                }
146
            }
147

    
148
            logout(service, requestId);
149
            if( !this.isUserCancelledWaitingForResponse() ) {
150
                authorize(service, requestId);
151
            }
152

    
153
            applicationToFront();
154
            return this.credentials!=null;
155
        } catch (Exception ex) {
156
            LOGGER.warn("Can't request authorization", ex);
157
            return false;
158
        }
159
    }
160

    
161
    private void applicationToFront() {
162
        try {
163
            java.awt.Window[] windows = Frame.getOwnerlessWindows();            
164
            if( windows != null ) {
165
                for (java.awt.Window window : windows) {
166
                    try {
167
                        window.toFront();
168
                    } catch(Exception ex) {
169
                        // Do nothing
170
                        LOGGER.debug("Can't bring to from "+window.toString());
171
                    }
172
                }
173
            }
174
        } catch(Exception e) {
175
            // Do nothing
176
            LOGGER.debug("Can't bring to from application");
177
        }
178
    }
179
    
180
    private OAuth20Service createService(String authCallback) {
181
        final String baseUrl = this.getConfig().getBaseurl();
182
        final String realm = this.getConfig().getRealm();
183
        final String scope = this.getConfig().getScope(); //"offline_access";
184

    
185
        final OAuth20Service service = new ServiceBuilder(getConfig().getClientid())
186
                .apiSecretIsEmptyStringUnsafe()
187
                .defaultScope(scope)
188
                .callback(authCallback)
189
                .build(KeycloakApi2.instance(baseUrl, realm));
190
        return service;
191
    }
192

    
193
    private void authorize(OAuth20Service service, String requestId) throws IOException, URISyntaxException {
194
        DownloaderAuthenticationKeycloakFactory factory = this.getConfig().getFactory();
195
        String contextPath = "/auth_" + requestId;
196
        CallbackAuthorizationHandler callbackHandler = new CallbackAuthorizationHandler(this, service, contextPath);
197
//        LOGGER.info("authorize "+requestId);
198
        try {
199
            factory.addCallback(getConfig(), contextPath, callbackHandler);
200
            final String authorizationUrl = service.getAuthorizationUrl();
201
            browse(authorizationUrl);
202
            this.waitForResponse();
203
        } catch(Exception e) {
204
            LOGGER.warn("Can't authorize",e);
205
            
206
        } finally {
207
//            LOGGER.info("authorize finally2 "+requestId);
208
            callbackHandler.remove();
209
//            LOGGER.info("authorize finally2 "+requestId);
210
        }
211
    }
212

    
213
    private void logout(OAuth20Service service, String requestId) throws URISyntaxException, IOException {
214
        KeycloakApi2 api = (KeycloakApi2) service.getApi();
215

    
216
        DownloaderAuthenticationKeycloakFactory factory = this.getConfig().getFactory();
217
        String contextPath = "/logout_" + requestId;
218
        CallbackLogoutHandler callbackHandler = new CallbackLogoutHandler(this, service, contextPath);
219
//        LOGGER.info("logout "+requestId);
220
        try {
221
            URLCodec urlcodec = new URLCodec();
222
            String redirect_uri = urlcodec.encode("http://localhost:" + getConfig().getLocalPort() + "/logout_"+requestId);
223
            String client_id = getConfig().getClientid();
224
            
225
            factory.addCallback(getConfig(), contextPath, callbackHandler);
226
            String logoutEndpoint = api.getLogoutEndpoint(client_id, redirect_uri);
227
            browse(logoutEndpoint);
228
            this.waitForResponse();
229
        } catch(Exception e) {
230
            LOGGER.warn("Can't logout",e);
231
            
232
        } finally {
233
//            LOGGER.info("logout finally1 "+requestId);
234
            callbackHandler.remove();
235
//            LOGGER.info("logout finally2 "+requestId);
236
        }
237
    }
238
    
239
    private void browse(String url) throws URISyntaxException, IOException {
240
//        Desktop.getDesktop().browse(new URI(url));
241
        DesktopOpen desktop = ToolsUtilLocator.getToolsUtilManager().createDesktopOpen();
242
        System.out.println("BROWSE : " + url);
243
        Toolkit defaultToolkit = Toolkit.getDefaultToolkit();
244
        desktop.browse(new URI(url));
245
    }
246

    
247
    public JsonObject  userInfo(OAuth20Service service) throws InterruptedException, IOException, ExecutionException {
248
//        LOGGER.info("userInfo");
249
        KeycloakApi2 api = (KeycloakApi2) service.getApi();
250
        final String userInfoEndpoint = api.getUserInfoEndpoint();
251
        System.out.println("userInfoEndpoint: "+userInfoEndpoint);
252
        final OAuthRequest request = new OAuthRequest(Verb.GET, userInfoEndpoint);
253
        service.signRequest(this.getCredentials().getToken(), request);
254
        try (final Response response = service.execute(request)) {
255
            System.out.println("userInfo-response: "+response.toString());
256
            if (response.getCode() == 200) {
257
                JsonObject userinfo = Json.createObject(response.getBody());
258
//                    System.out.println("userid: " + userinfo.getString("preferred_username", null);
259
//                    System.out.println("name: " + userinfo.getString("name", null));
260
//                    System.out.println("email: " + userinfo.getString("email", null));
261
//                    System.out.println("roles: " + userinfo.get("gvsigol_roles").toString());
262
//                    System.out.println("grupos: " + userinfo.get("groups").toString());
263
                return userinfo;
264
            }
265
        }
266
        return null;
267
    }
268

    
269
    public void waitForResponse() {
270
//        LOGGER.info("waitForTheResponse");
271
        synchronized (this.monitor) {
272
            ThreadSafeDialogsManager dialogs = ToolsSwingLocator.getThreadSafeDialogsManager();
273
            I18nManager i18n = ToolsLocator.getI18nManager();
274
            try {
275
                this.stopedWaitingForResponse = false;
276
                this.userCancelledWaitingForResponse = false;
277
                while( !this.userCancelledWaitingForResponse && !this.stopedWaitingForResponse) {
278
                    this.monitor.wait(20 * 1000);
279
                    if( !this.stopedWaitingForResponse ) {
280
                        int n = dialogs.confirmDialog(
281
                                i18n.getTranslation("_Do_you_want_to_continue_waiting_for_the_authentication_to_complete"), 
282
                                i18n.getTranslation("_Authentication"), 
283
                                JOptionPane.YES_NO_OPTION, 
284
                                JOptionPane.QUESTION_MESSAGE
285
                        );
286
                        if( n == JOptionPane.NO_OPTION ) {
287
                            this.userCancelledWaitingForResponse = true;
288
                        }
289
                    }
290
                }
291
            } catch (InterruptedException ex) {
292

    
293
            }
294
        }
295
//        LOGGER.info("waitForTheResponse exit");
296
    }
297

    
298
    public void stopWaitingForResponse() {
299
//        LOGGER.info("stopWaitingForResponse");
300
        synchronized (this.monitor) {
301
            this.stopedWaitingForResponse = true;
302
            this.monitor.notifyAll();
303
        }
304
//        LOGGER.info("stopWaitingForResponse exit");
305
    }
306
    
307
    private boolean isUserCancelledWaitingForResponse() {
308
        return this.userCancelledWaitingForResponse;
309
    }
310

    
311
    public DownloaderKeycloakCredentials getCredentials() {
312
        return credentials;
313
    }
314

    
315
    
316
    public void setCredentials(DownloaderKeycloakCredentials credentials) {
317
        this.saveCredetials(credentials);
318
        this.credentials = credentials;
319
    }
320

    
321
    private DownloaderKeycloakCredentials retrieveCredentials() {
322
        DownloaderManager manager = DownloaderLocator.getDownloaderManager();
323
        try {
324
            return (DownloaderKeycloakCredentials) manager.getCredentials(this.config.getServiceUrl());
325
        } catch(Exception ex) {
326
            return null;
327
        }
328
    }
329

    
330
    private void removeCredetials() {
331
        DownloaderManager manager = DownloaderLocator.getDownloaderManager();
332
        manager.removeCredentials(credentials);
333
    }
334

    
335
    private void saveCredetials(DownloaderKeycloakCredentials credentials) {
336
        DownloaderManager manager = DownloaderLocator.getDownloaderManager();
337
        manager.addOrReplaceCredentials(credentials);
338
    }
339

    
340
//    public static void main(String... args) throws Exception {
341
//        new DefaultLibrariesInitializer().fullInitialize();
342
//
343
//        UserIdentificationKeycloakFactory factory = new UserIdentificationKeycloakFactory();
344
//
345
////        UserIdentificationKeycloakConfig conf = factory.create("https://devel.gvsigonline.com/gvsigonline");
346
////        conf.setBaseurl("https://keycloak.scolab.eu/auth/realms");
347
////        conf.setRealm("joaquin");
348
////        conf.setClientid("gvsigdesktop");
349
//
350
//        DownloaderAuthenticationKeycloakConfig conf = factory.create("https://catastrord.gvsig-services.com/gvsigonline");
351
//        conf.setBaseurl("https://keycloak.gvsig-services.com/realms");
352
//        conf.setRealm("catastrord");
353
//        conf.setClientid("gvsigdesktop");
354
//        
355
//        OnlineUserIdentificationRequester requester = conf.createUserIdentificationRequester();
356
//
357
//        if (requester.requestIdentification()) {
358
//            System.out.println("Identificacion concluida.");
359
//            System.out.println("Credentials: " + requester.getCredentials());
360
//        } else {
361
//            System.out.println("Identificacion fallida.");
362
//        }
363
//
364
//        Thread.sleep(4000);
365
//
366
//        if (requester.requestIdentification()) {
367
//            System.out.println("Identificacion concluida.");
368
//            System.out.println("Credentials: " + requester.getCredentials());
369
//        } else {
370
//            System.out.println("Identificacion fallida.");
371
//        }
372
//
373
//        factory.stopHttpServer(1);
374
//    }
375

    
376
}