/* * Copyright 2000-2013 JetBrains s.r.o. * * 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 jetbrains.buildServer.buildTriggers.url; import com.google.gson.Gson; import com.google.gson.annotations.SerializedName; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.io.StreamUtil; import com.intellij.util.containers.ConcurrentWeakHashMap; import jetbrains.buildServer.util.StringUtil; import org.apache.http.NameValuePair; import org.apache.http.auth.Credentials; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.message.BasicNameValuePair; import org.jetbrains.annotations.NotNull; import java.io.IOException; import java.io.InputStreamReader; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; public class OAuthCredentialsProvider { @NotNull private final HttpClientProvider httpClientProvider; @NotNull private final Gson gson = new Gson(); @NotNull private final Map cachedCredentialsMap = new ConcurrentWeakHashMap<>(); private final static Logger LOG = LoggerFactory.getLogger(OAuthCredentialsProvider.class); public OAuthCredentialsProvider(@NotNull final HttpClientProvider httpClientProvider) { this.httpClientProvider = httpClientProvider; } public Credentials getCredentials(@NotNull final String oauthUrl, @NotNull final String clientId, @NotNull final String clientSecret, final int connectionTimeout) throws Exception { CredentialsDTO cachedCredentials = cachedCredentialsMap.get(oauthUrl); if (cachedCredentials != null) { if (cachedCredentials.isExpired()) { LOG.warn("OAuth credentials expired for " + oauthUrl + ": retrieved at " + cachedCredentials.retrievedAt + ", ttl " + cachedCredentials.expiresIn + ", now " + System.nanoTime()); cachedCredentialsMap.remove(oauthUrl); } else { return new BearerTokenCredentials(cachedCredentials.accessToken); } } try (CloseableHttpClient client = httpClientProvider.createClient(new URL(oauthUrl).toURI(), new UsernamePasswordCredentials(clientId, clientSecret), connectionTimeout)) { HttpPost post = new HttpPost(new URL(oauthUrl).toURI()); post.addHeader("Content-Type", "application/x-www-form-urlencoded"); List nvps = new ArrayList<>(); nvps.add(new BasicNameValuePair("client_id", clientId)); nvps.add(new BasicNameValuePair("client_secret", clientSecret)); nvps.add(new BasicNameValuePair("grant_type", "client_credentials")); post.setEntity(new UrlEncodedFormEntity(nvps, "UTF-8")); CloseableHttpResponse response = client.execute(post); if (response.getStatusLine().getStatusCode() != 200) { throw new IOException("Got unexpected code " + response.getStatusLine().getStatusCode() + "; " + StringUtil.truncateStringValue(StreamUtil.readText(response.getEntity().getContent()), 100)); } CredentialsDTO credentialsDTO = gson.fromJson(new InputStreamReader(response.getEntity().getContent()), CredentialsDTO.class); credentialsDTO.retrievedAt = System.nanoTime(); // gson creates the object through some dirty reflection cachedCredentialsMap.put(oauthUrl, credentialsDTO); return new BearerTokenCredentials(credentialsDTO.accessToken); } } private static class CredentialsDTO { @SerializedName("access_token") public final String accessToken; @SerializedName("token_type") public final String type; @SerializedName("expires_in") public final int expiresIn; transient public long retrievedAt; private CredentialsDTO(String accessToken, String type, int expiresIn) { this.accessToken = accessToken; this.type = type; this.expiresIn = expiresIn; } public boolean isExpired() { return expiresIn > 0 && retrievedAt + TimeUnit.SECONDS.toNanos(expiresIn / 2) < System.nanoTime(); } @Override public String toString() { return "CredentialsDTO{" + "accessToken='" + accessToken + '\'' + ", type='" + type + '\'' + ", expiresIn=" + expiresIn + '}'; } } }