/**
*
*
* @author Robert Jackson - nulli.com
* @version 1.0
*/
package org.forgerock.openicf.connectors.webtimesheet;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.Iterator;
import java.util.Set;
import org.apache.http.HttpHost;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.AuthCache;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.BasicAuthCache;
import org.apache.http.protocol.BasicHttpContext;
import org.identityconnectors.common.logging.Log;
import org.identityconnectors.common.security.GuardedString;
import org.identityconnectors.framework.common.exceptions.*;
import org.identityconnectors.framework.common.objects.Attribute;
import org.identityconnectors.framework.common.objects.Uid;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
/**
* Class to represent a WebTimeSheet Connection using new RepliConnect API
* (Replaces RTAPI API). Apache HTTP Client is used to send XML requests to the
* Web TimeSheet server. <br/></br/> Web TimeSheet (WTS) refers to a suite of
* products offered by <a href='http://www.replicon.com'>Replicon Inc.</a>
* <br/><br/> RepliConnect API Reference found <a
* href='http://www.replicon.com/repliconnect'>here</a> <br/><br/> Currently,
* client supports Create/Update/Delete/Searching of user objects and Searching
* of Department objects <br/><br/>
*
* @author Robert Jackson - <a href='http://www.nulli.com'>Nulli</a>
*/
public class RepliConnectClient {
/**
* Setup logging for the {@link RepliConnectClient}.
*/
private static final Log log = Log.getLog(RepliConnectClient.class);
//private DefaultHttpClient _client;
private final String _uri;
private final UsernamePasswordCredentials credentials;
//private HttpPost _httpPost;
private final HttpHost _targetHost;
private final BasicHttpContext _httpContext;
/**
* Client Constructor
*
* @param cfg
* the WTS service configuration
*
* @throws ConnectorIOException
*/
public RepliConnectClient(WebTimeSheetConfiguration cfg) throws ConnectorIOException {
_uri = cfg.getWtsURI();
credentials = new UsernamePasswordCredentials(cfg.getAdminUid(), getPlainPassword(cfg.getAdminPassword()));
_targetHost = new HttpHost(cfg.getWtsHost(), cfg.getWtsPort(), "https"); //hard-coded to HTTPS for now.. assume everyone would use it
// Create AuthCache instance
AuthCache authCache = new BasicAuthCache();
// Generate BASIC scheme object and add it to the local auth cache
BasicScheme basicAuth = new BasicScheme();
authCache.put(_targetHost, basicAuth);
// Add AuthCache to the execution context
_httpContext = new BasicHttpContext();
_httpContext.setAttribute(ClientContext.AUTH_CACHE, authCache);
log.info("Creating POST action for {0}", _targetHost.getSchemeName() + "://" + _targetHost.toHostString() + _uri);
//Connect();
}
private String getPlainPassword(final GuardedString password) {
if (password == null) {
return null;
}
final StringBuffer buf = new StringBuffer();
password.access(new GuardedString.Accessor() {
public void access(char[] clearChars) {
buf.append(clearChars);
}
});
return buf.toString();
}
/**
* Release internal resources
*/
public void dispose() {
}
/*
* Performs a simple test to ensure that the application is registered and authorized with the Web TimeSheet Service
*/
public void testConnection() {
//fetch self
JSONObject query = new JSONObject();
try {
query.put("Action", "Query");
query.put("DomainType", "Replicon.Domain.User");
query.put("QueryType", "UserByLoginName");
query.put("Args", new JSONArray().put(credentials.getUserName()));
}
catch (JSONException ex) {
log.error("Unable to prepare JSON query", ex);
}
JSONObject res = this.getUser(query.toString());
}
/*
* Lists users with optional query
*
* @param query User query string
*
**/
public JSONObject listUsers(String queryString) {
JSONObject query = new JSONObject();
try {
query.put("Action", "Query");
query.put("DomainType", "Replicon.Domain.User");
query.put("QueryType", "UserAll");
query.put("Args", new JSONArray());
}
catch (JSONException ex) {
log.error("Unable to prepare JSON request", ex);
}
return this.call(query);
}
/*
* Gets a single user record
*
* @param query User query string
*
**/
public JSONObject getUser(String queryString) {
JSONObject query = new JSONObject();
try {
/*
query.put("Action", "Query");
query.put("DomainType", "Replicon.Domain.User");
query.put("QueryType", "UserByLoginName");
query.put("Args", new JSONArray().put(queryString));
*/
JSONTokener jt = new JSONTokener(queryString);
query = new JSONObject(jt);
}
catch (JSONException ex) {
log.error("Unable to prepare JSON request", ex);
}
return this.call(query);
}
/*
* Creates a new user record
*
* @param attrs Set of Attributes for new user
* @param deptId id of the PrimaryDepartment for new user
*
**/
public Uid createUser(Set<Attribute> attrs, String deptId) {
JSONObject user = new JSONObject();
try {
user.put("Action", "Create");
user.put("Type", "Replicon.Domain.User");
JSONObject op = new JSONObject();
op.put("__operation", "SetProperties");
JSONObject dept = new JSONObject();
dept.put("__type", "Replicon.Domain.Department");
dept.put("Identity", deptId);
op.put("PrimaryDepartment", dept);
Iterator aitr = attrs.iterator();
while (aitr.hasNext()) {
Attribute attr = (Attribute)aitr.next();
if (!(attr.getName().startsWith("__"))) {
op.put(attr.getName(), attr.getValue().get(0));
} else if (attr.getName().equalsIgnoreCase("__PASSWORD__")) {
op.put("Password", attr.getValue().get(0));
}
}
JSONArray ops = new JSONArray();
ops.put(op);
user.put("Operations", ops);
}
catch (JSONException ex) {
log.error("Unable to prepare JSON request", ex);
}
String newuid = null;
JSONObject newuser = this.call(user);
try {
JSONArray users = newuser.getJSONArray("Value");
newuid = users.getJSONObject(0).getString("Identity");
}
catch (JSONException ex) {
/* ignore */
}
return new Uid(newuid);
}
/*
* Updates a user record
*
* @param attrs Set of Attributes for new user
* @param uid id of the user
*
**/
public Uid updateUser(String uid, Set<Attribute> attrs) {
JSONObject user = new JSONObject();
try {
user.put("Action", "Edit");
user.put("Type", "Replicon.Domain.User");
user.put("Identity", uid);
JSONObject op = new JSONObject();
op.put("__operation", "SetProperties");
Iterator aitr = attrs.iterator();
while (aitr.hasNext()) {
Attribute attr = (Attribute)aitr.next();
op.put(attr.getName(), attr.getValue().get(0));
}
JSONArray ops = new JSONArray();
ops.put(op);
user.put("Operations", ops);
}
catch (JSONException ex) {
log.error("Unable to prepare JSON request", ex);
}
JSONObject result = this.call(user);
String updateduid = null;
try {
JSONArray users = result.getJSONArray("Value");
updateduid = users.getJSONObject(0).getString("Identity");
}
catch (JSONException ex) {
/* ignore */
}
return new Uid(updateduid);
}
/*
* Deletes a user record
*
* @param uid user account to delete
*
**/
public void deleteUser(String uid) {
JSONObject delete = new JSONObject();
try {
delete.put("Action", "Delete");
delete.put("Type", "Replicon.Domain.User");
delete.put("Identity", uid);
}
catch (JSONException ex) {
log.error("Unable to prepare JSON request", ex);
}
this.call(delete);
//add some error handling
}
protected JSONObject call(JSONObject command) throws RuntimeException {
log.info("Posting request: {0}", command.toString());
HttpPost postAction;
DefaultHttpClient client = new DefaultHttpClient();
try {
client = new DefaultHttpClient();
}
catch (java.lang.NoClassDefFoundError ex) {
throw new ConnectorIOException("Missing Apache HTTPClient Library (or dependency)");
}
client.getCredentialsProvider().setCredentials(
new AuthScope(_targetHost.getHostName(), _targetHost.getPort()),
credentials);
postAction = new HttpPost(_targetHost.getSchemeName() + "://" + _targetHost.toHostString() + _uri);
log.info("Constructed URL: {0}", _targetHost.getSchemeName() + "://" + _targetHost.toHostString() + _uri);
postAction.addHeader("Content-type", "application/JSON");
// User mode is new but makes objects read-only - don't want that
// need to get Replicon Support to unhide the "Can view all system data" permission
// http://www.replicon.com/kb-2934
//postAction.addHeader("X-Replicon-Security-Context", "User");
try {
postAction.setEntity(new StringEntity(command.toString()));
log.info("Added payload: {0}", command.toString());
HttpResponse res = client.execute(postAction, _httpContext);
int statusCode = res.getStatusLine().getStatusCode();
log.info("HTTP Response Code: {0}", statusCode);
if (statusCode != 200) {
throw new org.identityconnectors.framework.common.exceptions.ConnectionFailedException("HTTP code:" + statusCode);
} else {
//res.getEntity().writeTo(System.out);
try {
Reader reader = new InputStreamReader(res.getEntity().getContent());
JSONTokener jt = new JSONTokener(reader);
JSONObject response = new JSONObject(jt);
log.info("JSONResponse: {0}", response.toString());
log.info("RepliConnect call Status: {0}", response.getString("Status"));
if (response.getString("Status").equalsIgnoreCase("Exception")) {
throw new RuntimeException("Error in RepliConnect Call: " + response.getString("Message"));
}
return response;
}
catch (JSONException ex) {
log.error("Unable to Tokenize JSON response", ex);
}
}
return null;
}
catch (IOException ex) {
log.error("Error Occured Posting Request", ex);
throw new RuntimeException(ex);
}
}
}