/*
* Copyright (c) 2013 Intellectual Reserve, Inc. All rights reserved.
*
* 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 cf.client;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.*;
import cf.client.model.UaaUser;
import com.fasterxml.jackson.databind.JsonNode;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.HttpClientUtils;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* @author Mike Heath
*/
public class DefaultUaa implements Uaa {
private static final String CHECK_TOKEN = "/check_token";
private static final String OAUTH_TOKEN_URI = "/oauth/token";
private static final String USERS_URI = "/Users";
private static final Header ACCEPT_JSON = new BasicHeader("Accept","application/json;charset=utf-8");
private final HttpClient httpClient;
private final URI uaa;
private final ObjectMapper mapper;
public DefaultUaa(HttpClient httpClient, String uaaUri) {
this(httpClient, URI.create(uaaUri));
}
public DefaultUaa(HttpClient httpClient, URI uaa) {
if(uaa == null) {
throw new IllegalArgumentException("Null uaa url is invalid.");
}
this.httpClient = httpClient;
this.uaa = uaa;
mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
@Override
public Token getClientToken(String client, String clientSecret) {
try {
final HttpPost post = new HttpPost(uaa.resolve(OAUTH_TOKEN_URI));
post.setHeader(ACCEPT_JSON);
post.setHeader(createClientCredentialsHeader(client, clientSecret));
// TODO Do we need to make the grant type configurable?
final BasicNameValuePair nameValuePair = new BasicNameValuePair("grant_type", "client_credentials");
post.setEntity(new UrlEncodedFormEntity(Arrays.asList(nameValuePair)));
final HttpResponse response = httpClient.execute(post);
try {
validateResponse(response);
final HttpEntity entity = response.getEntity();
final InputStream content = entity.getContent();
return Token.parseJson(content);
} finally {
HttpClientUtils.closeQuietly(response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public Token getUserToken(String client, String username, String password) {
try {
final HttpPost post = new HttpPost(uaa.resolve(OAUTH_TOKEN_URI));
post.setHeader(ACCEPT_JSON);
post.setHeader(createClientCredentialsHeader(client, ""));
// TODO Do we need to make the grant type configurable?
final BasicNameValuePair grantTypePair = new BasicNameValuePair("grant_type", "password");
final BasicNameValuePair usernamePair = new BasicNameValuePair("username", username);
final BasicNameValuePair passwordPair = new BasicNameValuePair("password", password);
final BasicNameValuePair scopePair = new BasicNameValuePair("scope", "");
post.setEntity(new UrlEncodedFormEntity(Arrays.asList(grantTypePair, usernamePair, passwordPair, scopePair)));
final HttpResponse response = httpClient.execute(post);
try {
validateResponse(response);
final HttpEntity entity = response.getEntity();
final InputStream content = entity.getContent();
return Token.parseJson(content);
} finally {
HttpClientUtils.closeQuietly(response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public TokenContents checkToken(String client, String clientSecret, Token token) {
try {
final URI checkTokenUri = uaa.resolve(CHECK_TOKEN);
final HttpPost post = new HttpPost(checkTokenUri);
post.setHeader(createClientCredentialsHeader(client,clientSecret));
final NameValuePair tokenType = new BasicNameValuePair("token_type", token.getType().getValue());
final NameValuePair tokenValue = new BasicNameValuePair("token", token.getAccessToken());
post.setEntity(new UrlEncodedFormEntity(Arrays.asList(tokenType, tokenValue)));
final HttpResponse response = httpClient.execute(post);
try {
validateResponse(response);
final HttpEntity entity = response.getEntity();
final InputStream content = entity.getContent();
return mapper.readValue(content, TokenContents.class);
} finally {
HttpClientUtils.closeQuietly(response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public UaaUser getUser(Token token, String username) {
try {
final URI usersUri = uaa.resolve(USERS_URI + "?filter=userName%20eq%20%22" + username + "%22");
final HttpGet get = new HttpGet(usersUri);
get.setHeader(token.toAuthorizationHeader());
HttpResponse response = httpClient.execute(get);
try {
validateResponse(response);
JsonNode jsonNode = mapper.readTree(response.getEntity().getContent());
int totalResults = jsonNode.get("totalResults").asInt();
if (totalResults == 1) {
JsonNode userJson = jsonNode.get("resources").elements().next();
return mapper.readValue(userJson.traverse(), UaaUser.class);
} else if (totalResults == 0) {
return null;
} else {
throw new RuntimeException("Error retrieving user from uaa. Expected 1 result but found " + totalResults);
}
} finally {
HttpClientUtils.closeQuietly(response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public UUID createUser(Token token, String username, String password, String origin) {
Map<String, Object> email = new HashMap<>();
email.put("primary", Boolean.TRUE);
email.put("value", username);
Map<String, String> name = new HashMap<>();
name.put("familyName", username);
name.put("givenName", username);
Map<String, Object> user = new HashMap<>();
user.put("emails", Collections.singletonList(email));
user.put("name", name);
user.put("origin", origin);
user.put("password", password);
user.put("userName", username);
try {
final String requestString = mapper.writeValueAsString(user);
final HttpPost post = new HttpPost(uaa.resolve(USERS_URI));
post.addHeader(token.toAuthorizationHeader());
post.setEntity(new StringEntity(requestString, ContentType.APPLICATION_JSON));
final HttpResponse response = httpClient.execute(post);
try {
validateResponse(response, 201);
final JsonNode responseJson = mapper.readTree(response.getEntity().getContent());
return UUID.fromString(responseJson.get("id").asText());
} finally {
HttpClientUtils.closeQuietly(response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private Header createClientCredentialsHeader(String client, String clientSecret) {
final String encoding = Base64.encodeBase64String((client + ":" + clientSecret).getBytes());
return new BasicHeader("Authorization", "Basic " + encoding);
}
private Header createClientHeader(String client) {
final String encoding = Base64.encodeBase64String((client).getBytes());
return new BasicHeader("Authorization", "Basic " + encoding);
}
private void validateResponse(HttpResponse response) {
validateResponse(response, 200);
}
private void validateResponse(HttpResponse response, int expectedStatusCode) {
final StatusLine statusLine = response.getStatusLine();
final int statusCode = statusLine.getStatusCode();
if (statusCode != expectedStatusCode) {
throw new UnexpectedResponseException(response);
}
}
}