/*
* Copyright 2014-2016 CyberVision, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.kaaproject.kaa.server.verifiers.gplus.verifier;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.codehaus.jackson.map.ObjectMapper;
import org.kaaproject.kaa.server.common.verifier.AbstractKaaUserVerifier;
import org.kaaproject.kaa.server.common.verifier.UserVerifierCallback;
import org.kaaproject.kaa.server.common.verifier.UserVerifierContext;
import org.kaaproject.kaa.server.verifiers.gplus.config.gen.GplusAvroConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class GplusUserVerifier extends AbstractKaaUserVerifier<GplusAvroConfig> {
private static final Logger LOG = LoggerFactory.getLogger(GplusUserVerifier.class);
private static final String GOOGLE_OAUTH = "https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=";
private static final int HTTP_OK = 200;
private static final int HTTP_BAD_REQUEST = 400;
private static final Charset CHARSET = Charset.forName("UTF-8");
private GplusAvroConfig configuration;
private ExecutorService threadPool;
private volatile CloseableHttpClient httpClient;
@Override
public void init(UserVerifierContext context, GplusAvroConfig configuration) {
LOG.info("Initializing user verifier with context {} and configuration {}", context, configuration);
this.configuration = configuration;
}
@Override
public void checkAccessToken(String userExternalId, String accessToken, UserVerifierCallback callback) {
try {
URI uri = new URI(GOOGLE_OAUTH + accessToken);
threadPool.submit(new RunnableVerifier(uri, callback, userExternalId));
} catch (URISyntaxException ex) {
LOG.warn("Internal error", ex);
callback.onInternalError();
}
}
protected String readResponse(InputStream is) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] data = new byte[2048];
int len = 0;
while ((len = is.read(data, 0, data.length)) >= 0) {
bos.write(data, 0, len);
}
byte[] bytes = bos.toByteArray();
bos.close();
return new String(bytes, CHARSET);
}
protected CloseableHttpResponse establishConnection(URI uri) throws IOException {
return httpClient.execute(new HttpGet(uri));
}
@Override
public void start() {
LOG.info("user verifier started");
threadPool = new ThreadPoolExecutor(configuration.getMinParallelConnections(), configuration.getMaxParallelConnections(),
configuration.getKeepAliveTimeMilliseconds(), TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(configuration.getMaxParallelConnections());
httpClient = HttpClients.custom().setConnectionManager(connectionManager).build();
}
@Override
public void stop() {
threadPool.shutdown();
if (null != httpClient) {
try {
httpClient.close();
} catch (IOException ex) {
LOG.warn("Internal error: ", ex);
}
}
LOG.info("user verifier stopped");
}
@Override
public Class<GplusAvroConfig> getConfigurationClass() {
return GplusAvroConfig.class;
}
private class RunnableVerifier implements Runnable {
private final URI uri;
private final UserVerifierCallback callback;
private final String userExternalId;
public RunnableVerifier(URI uri, UserVerifierCallback callback, String userExternalId) {
this.uri = uri;
this.callback = callback;
this.userExternalId = userExternalId;
}
@Override
public void run() {
CloseableHttpResponse closeableHttpResponse = null;
try {
String responseJson;
int responseCode;
closeableHttpResponse = establishConnection(uri);
responseCode = closeableHttpResponse.getStatusLine().getStatusCode();
if (responseCode == HTTP_OK) {
responseJson = readResponse(closeableHttpResponse.getEntity().getContent());
ObjectMapper mapper = new ObjectMapper();
Map map = mapper.readValue(responseJson, Map.class);
String userId = String.valueOf(map.get("user_id"));
if (!userExternalId.equals(userId)) {
LOG.trace("Input token doesn't belong to the user with {} id", userExternalId);
callback.onVerificationFailure("User access token doesn't belong to the user");
} else {
LOG.trace("Input token is confirmed and belongs to the user with {} id", userExternalId);
callback.onSuccess();
}
} else if (responseCode == HTTP_BAD_REQUEST) {
LOG.trace("Server auth error: {}", readResponse(closeableHttpResponse.getEntity().getContent()));
callback.onTokenInvalid();
} else {
LOG.trace("Server returned the following error code: {}", responseCode);
callback.onInternalError();
}
} catch (IOException ex) {
LOG.warn("Internal error: ", ex);
callback.onInternalError();
} catch (Exception ex) {
LOG.warn("Internal error: ", ex);
callback.onInternalError();
} finally {
if (null != closeableHttpResponse) {
try {
closeableHttpResponse.close();
} catch (IOException ex) {
LOG.warn("Internal error: ", ex);
}
}
}
}
}
}