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 @ 47833

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 org.apache.commons.codec.net.URLCodec;
42
import org.apache.commons.lang.StringUtils;
43
import org.gvsig.desktopopen.DesktopOpen;
44
import org.gvsig.downloader.DownloaderAuthenticationRequester;
45
import org.gvsig.downloader.DownloaderLocator;
46
import org.gvsig.downloader.DownloaderManager;
47
import org.gvsig.downloader.spi.AbstractDownloaderAuthenticationRequester;
48
import org.gvsig.downloader.swing.scribejava.keycloak.callbacks.CallbackAuthorizationHandler;
49
import org.gvsig.downloader.swing.scribejava.keycloak.callbacks.CallbackLogoutHandler;
50
import org.gvsig.json.Json;
51
import org.gvsig.tools.ToolsLocator;
52
import org.gvsig.tools.i18n.I18nManager;
53
import org.gvsig.tools.swing.api.ToolsSwingLocator;
54
import org.gvsig.tools.swing.api.threadsafedialogs.ThreadSafeDialogsManager;
55
import org.gvsig.tools.util.ToolsUtilLocator;
56
import org.slf4j.Logger;
57
import org.slf4j.LoggerFactory;
58

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

    
68
    private static final Logger LOGGER = LoggerFactory.getLogger(DownloaderAuthenticationKeycloakRequester.class);
69

    
70
    private final Object monitor = new Object();
71
    private boolean stopedWaitingForResponse = false;
72
    private boolean userCancelledWaitingForResponse = false;
73

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
291
            }
292
        }
293
//        LOGGER.info("waitForTheResponse exit");
294
    }
295

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

    
309
    public DownloaderKeycloakCredentials getCredentials() {
310
        return credentials;
311
    }
312

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

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

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

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

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

    
374
}