/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.manifoldcf.authorities.authorities.amazons3;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.apache.manifoldcf.authorities.interfaces.AuthorizationResponse;
import org.apache.manifoldcf.authorities.system.Logging;
import org.apache.manifoldcf.core.interfaces.ConfigNode;
import org.apache.manifoldcf.core.interfaces.ConfigParams;
import org.apache.manifoldcf.core.interfaces.IHTTPOutput;
import org.apache.manifoldcf.core.interfaces.IPasswordMapperActivity;
import org.apache.manifoldcf.core.interfaces.IPostParameters;
import org.apache.manifoldcf.core.interfaces.IThreadContext;
import org.apache.manifoldcf.core.interfaces.ManifoldCFException;
import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.AccessControlList;
import com.amazonaws.services.s3.model.Bucket;
import com.amazonaws.services.s3.model.CanonicalGrantee;
import com.amazonaws.services.s3.model.Grant;
import com.amazonaws.services.s3.model.Grantee;
import com.amazonaws.services.s3.model.Owner;
/**
* Authority connector for Amazons3
* @author Kuhajeyan
*
*/
public class AmazonS3Authority extends org.apache.manifoldcf.authorities.authorities.BaseAuthorityConnector {
private static final String TAB_NAME = "TabName";
protected long lastSessionFetch = -1L;
protected static final long timeToRelease = 300000L;
protected AmazonS3 amazonS3;
protected boolean connected = false;
protected String amazons3ProxyHost = null;
protected String amazons3ProxyPort = null;
protected String amazons3ProxyDomain = null;
protected String amazons3ProxyUserName = null;
protected String amazons3ProxyPassword = null;
protected String amazons3AwsAccessKey = null;
protected String amazons3AwsSecretKey = null;
public AmazonS3Authority() {
}
@Override
public void disconnect() throws ManifoldCFException {
amazons3AwsAccessKey = null;
amazons3AwsSecretKey = null;
amazons3ProxyHost = null;
amazons3ProxyPort = null;
amazons3ProxyDomain = null;
amazons3ProxyUserName = null;
amazons3ProxyPassword = null;
}
@Override
public void connect(ConfigParams configParams) {
super.connect(configParams);
// aws access and secret keys
amazons3AwsAccessKey = configParams
.getParameter(AmazonS3Config.AWS_ACCESS_KEY);
amazons3AwsSecretKey = configParams
.getObfuscatedParameter(AmazonS3Config.AWS_SECRET_KEY);
// proxy values
amazons3ProxyHost = configParams
.getParameter(AmazonS3Config.AMAZONS3_PROXY_HOST);
amazons3ProxyPort = configParams
.getParameter(AmazonS3Config.AMAZONS3_PROXY_PORT);
amazons3ProxyDomain = configParams
.getParameter(AmazonS3Config.AMAZONS3_PROXY_DOMAIN);
amazons3ProxyUserName = configParams
.getParameter(AmazonS3Config.AMAZONS3_PROXY_USERNAME);
amazons3ProxyPassword = configParams
.getObfuscatedParameter(AmazonS3Config.AMAZONS3_PROXY_PASSWORD);
}
/**
* Test the connection. Returns a string describing the connection
* integrity.
*
* @return the connection's status as a displayable string.
*/
@Override
public String check() throws ManifoldCFException {
// connect with amazons3 client
Logging.authorityConnectors.info("Checking connection");
try {
// invokes the check thread
CheckThread checkThread = new CheckThread(getClient());
checkThread.start();
checkThread.join();
if (checkThread.getException() != null) {
Throwable thr = checkThread.getException();
return "Check exception: " + thr.getMessage();
}
return checkThread.getResult();
}
catch (InterruptedException ex) {
Logging.authorityConnectors.error(
"Error while checking connection", ex);
throw new ManifoldCFException(ex.getMessage(), ex,
ManifoldCFException.INTERRUPTED);
}
}
/**
* Get the Amazons3 client, relevant access keys should have been posted
* already
* @return
*/
protected AmazonS3 getClient() {
if (amazonS3 == null) {
try {
BasicAWSCredentials awsCreds = new BasicAWSCredentials(
amazons3AwsAccessKey, amazons3AwsSecretKey);
amazonS3 = new AmazonS3Client(awsCreds);
}
catch (Exception e) {
Logging.authorityConnectors.error(
"Error while amazon s3 connectionr", e);
}
}
lastSessionFetch = System.currentTimeMillis();
return amazonS3;
}
@Override
public boolean isConnected() {
return amazonS3 != null && amazonS3.getS3AccountOwner() != null;
}
@Override
public void poll() throws ManifoldCFException {
if (lastSessionFetch == -1L) {
return;
}
long currentTime = System.currentTimeMillis();
if (currentTime >= lastSessionFetch + timeToRelease) {
amazonS3 = null;
lastSessionFetch = -1L;
}
}
private void fillInServerConfigurationMap(Map<String, Object> out,
IPasswordMapperActivity mapper, ConfigParams parameters) {
String amazons3AccessKey = parameters
.getParameter(AmazonS3Config.AWS_ACCESS_KEY);
String amazons3SecretKey = parameters
.getObfuscatedParameter(AmazonS3Config.AWS_SECRET_KEY);
// default values
if (amazons3AccessKey == null)
amazons3AccessKey = AmazonS3Config.AMAZONS3_AWS_ACCESS_KEY_DEFAULT;
if (amazons3SecretKey == null)
amazons3SecretKey = AmazonS3Config.AMAZONS3_AWS_SECRET_KEY_DEFAULT;
else
amazons3SecretKey = mapper.mapPasswordToKey(amazons3SecretKey);
// fill the map
out.put("AMAZONS3_AWS_ACCESS_KEY", amazons3AccessKey);
out.put("AMAZONS3_AWS_SECRET_KEY", amazons3SecretKey);
}
private void fillInProxyConfigurationMap(Map<String, Object> out,
IPasswordMapperActivity mapper, ConfigParams parameters) {
String amazons3ProxyHost = parameters
.getParameter(AmazonS3Config.AMAZONS3_PROXY_HOST);
String amazons3ProxyPort = parameters
.getParameter(AmazonS3Config.AMAZONS3_PROXY_PORT);
String amazons3ProxyDomain = parameters
.getParameter(AmazonS3Config.AMAZONS3_PROXY_DOMAIN);
String amazons3ProxyUserName = parameters
.getParameter(AmazonS3Config.AMAZONS3_PROXY_USERNAME);
String amazons3ProxyPassword = parameters
.getObfuscatedParameter(AmazonS3Config.AMAZONS3_PROXY_PASSWORD);
if (amazons3ProxyHost == null)
amazons3ProxyHost = AmazonS3Config.AMAZONS3_PROXY_HOST_DEFAULT;
if (amazons3ProxyPort == null)
amazons3ProxyPort = AmazonS3Config.AMAZONS3_PROXY_PORT_DEFAULT;
if (amazons3ProxyDomain == null)
amazons3ProxyDomain = AmazonS3Config.AMAZONS3_PROXY_DOMAIN_DEFAULT;
if (amazons3ProxyUserName == null)
amazons3ProxyUserName = AmazonS3Config.AMAZONS3_PROXY_USERNAME_DEFAULT;
if (amazons3ProxyPassword == null)
amazons3ProxyPassword = AmazonS3Config.AMAZONS3_PROXY_PASSWORD_DEFAULT;
else
amazons3ProxyPassword = mapper
.mapPasswordToKey(amazons3ProxyPassword);
// fill the map
out.put("AMAZONS3_PROXY_HOST", amazons3ProxyHost);
out.put("AMAZONS3_PROXY_PORT", amazons3ProxyPort);
out.put("AMAZONS3_PROXY_DOMAIN", amazons3ProxyDomain);
out.put("AMAZONS3_PROXY_USERNAME", amazons3ProxyUserName);
out.put("AMAZONS3_PROXY_PWD", amazons3ProxyPassword);
}
/**
* View configuration. This method is called in the body section of the
* connector's view configuration page. Its purpose is to present the
* connection information to the user. The coder can presume that the HTML
* that is output from this configuration will be within appropriate <html>
* and <body> tags.
*
* */
public void viewConfiguration(IThreadContext threadContext,
IHTTPOutput out, Locale locale, ConfigParams parameters)
throws ManifoldCFException, IOException {
Map<String, Object> paramMap = new HashMap<String, Object>();
// Fill in map from each tab
fillInServerConfigurationMap(paramMap, out, parameters);
fillInProxyConfigurationMap(paramMap, out, parameters);
Messages.outputResourceWithVelocity(out, locale, AmazonS3Config.VIEW_CONFIG_FORWARD, paramMap);
}
/**
* Output the configuration header section. This method is called in the
* head section of the connector's configuration page. Its purpose is to add
* the required tabs to the list, and to output any javascript methods that
* might be needed by the configuration editing HTML.
* */
@Override
public void outputConfigurationHeader(IThreadContext threadContext,
IHTTPOutput out, Locale locale, ConfigParams parameters,
List<String> tabsArray) throws ManifoldCFException, IOException {
// Add the Server tab
tabsArray.add(Messages.getString(locale,
AmazonS3Config.AMAZONS3_SERVER_TAB_PROPERTY));
// Add the Proxy tab
tabsArray.add(Messages.getString(locale,
AmazonS3Config.AMAZONS3_PROXY_TAB_PROPERTY));
// Map the parameters
Map<String, Object> paramMap = new HashMap<String, Object>();
// Fill in the parameters from each tab
fillInServerConfigurationMap(paramMap, out, parameters);
fillInProxyConfigurationMap(paramMap, out, parameters);
// Output the Javascript - only one Velocity template for all tabs
Messages.outputResourceWithVelocity(out, locale,
AmazonS3Config.EDIT_CONFIG_HEADER_FORWARD, paramMap);
}
@Override
public void outputConfigurationBody(IThreadContext threadContext,
IHTTPOutput out, Locale locale, ConfigParams parameters,
String tabName) throws ManifoldCFException, IOException {
// Call the Velocity templates for each tab
Map<String, Object> paramMap = new HashMap<String, Object>();
// Set the tab name
paramMap.put(TAB_NAME, tabName);
// Fill in the parameters
fillInServerConfigurationMap(paramMap, out, parameters);
fillInProxyConfigurationMap(paramMap, out, parameters);
// Server tab
Messages.outputResourceWithVelocity(out, locale,
AmazonS3Config.EDIT_CONFIG_FORWARD_SERVER, paramMap);
// Proxy tab
Messages.outputResourceWithVelocity(out, locale,
AmazonS3Config.EDIT_CONFIG_FORWARD_PROXY, paramMap);
}
/**
* Process a configuration post. This method is called at the start of the
* connector's configuration page, whenever there is a possibility that form
* data for a connection has been posted. Its purpose is to gather form
* information and modify the configuration parameters accordingly. The name
* of the posted form is "editconnection".
* */
@Override
public String processConfigurationPost(IThreadContext threadContext,
IPostParameters variableContext, Locale locale,
ConfigParams parameters) throws ManifoldCFException {
// server tab
String awsAccessKey = variableContext.getParameter("aws_access_key");
if (awsAccessKey != null) {
parameters
.setParameter(AmazonS3Config.AWS_ACCESS_KEY, awsAccessKey);
}
String awsSecretKey = variableContext.getParameter("aws_secret_key");
if (awsSecretKey != null) {
// set as obfuscated parameter
parameters.setObfuscatedParameter(AmazonS3Config.AWS_SECRET_KEY,
variableContext.mapKeyToPassword(awsSecretKey));
}
Logging.authorityConnectors.info("Saved values for aws keys");
int i = 0;
while (i < parameters.getChildCount()) {
ConfigNode cn = parameters.getChild(i);
if (cn.getType().equals(AmazonS3Config.AWS_ACCESS_KEY)
|| cn.getType().equals(AmazonS3Config.AWS_SECRET_KEY))
parameters.removeChild(i);
else
i++;
}
// proxy tab
String amazons3ProxyHost = variableContext
.getParameter("amazons3_proxy_host");
if (amazons3ProxyHost != null) {
parameters.setParameter(AmazonS3Config.AMAZONS3_PROXY_HOST,
amazons3ProxyHost);
}
String amazons3ProxyPort = variableContext
.getParameter("amazons3_proxy_port");
if (amazons3ProxyPort != null) {
parameters.setParameter(AmazonS3Config.AMAZONS3_PROXY_PORT,
amazons3ProxyPort);
}
String amazons3ProxyDomain = variableContext
.getParameter("amazons3_proxy_domain");
if (amazons3ProxyDomain != null) {
parameters.setParameter(AmazonS3Config.AMAZONS3_PROXY_DOMAIN,
amazons3ProxyDomain);
}
String amazons3ProxyUserName = variableContext
.getParameter("amazons3_proxy_username");
if (amazons3ProxyUserName != null) {
parameters.setParameter(AmazonS3Config.AMAZONS3_PROXY_USERNAME,
amazons3ProxyUserName);
}
String amazons3ProxyPassword = variableContext
.getParameter("amazons3_proxy_pwd");
if (amazons3ProxyPassword != null) {
// set as obfuscated parameter
parameters.setObfuscatedParameter(
AmazonS3Config.AMAZONS3_PROXY_PASSWORD,
variableContext.mapKeyToPassword(amazons3ProxyPassword));
}
return null;
}
@Override
public AuthorizationResponse getAuthorizationResponse(String userName)
throws ManifoldCFException {
try {
HashMap<String, Set<Grant>> checkUserExists = checkUserExists(userName);
if (isUserAvailable(userName, checkUserExists.values())) {
return new AuthorizationResponse(new String[] { userName },
AuthorizationResponse.RESPONSE_OK);
}
}
catch (Exception e) {
Logging.authorityConnectors.error("Error while getting authorization response",e);
return RESPONSE_UNREACHABLE;
}
return RESPONSE_USERNOTFOUND;
}
private boolean isUserAvailable(String userName,
Collection<Set<Grant>> collection) {
String[] users = getUsers(collection);
return Arrays.asList(users).contains(userName);
}
private String[] getUsers(Collection<Set<Grant>> collection) {
Set<String> users = new HashSet<String>();// no duplicates
for (Collection c : collection) {
Set<Grant> c1 = (Set<Grant>) c;
for (Grant grant : c1) {
if (grant != null && grant.getGrantee() != null) {
Grantee grantee = grant.getGrantee();
if (grantee instanceof CanonicalGrantee) {
users.add(((CanonicalGrantee) grantee).getDisplayName());
}
else {
users.add(grantee.getIdentifier());
}
}
}
}
return users.toArray(new String[users.size()]);
}
private HashMap<String, Set<Grant>> checkUserExists(String userName)
throws ManifoldCFException {
GrantsThread t = new GrantsThread(getClient());
try {
t.start();
t.finishUp();
return t.getResult();
}
catch (InterruptedException e) {
t.interrupt();
throw new ManifoldCFException("Interrupted: " + e.getMessage(), e,
ManifoldCFException.INTERRUPTED);
}
catch (java.net.SocketTimeoutException e) {
handleIOException(e);
}
catch (InterruptedIOException e) {
t.interrupt();
handleIOException(e);
}
catch (IOException e) {
handleIOException(e);
}
catch (ResponseException e) {
handleResponseException(e);
}
return null;
}
/**
* Obtain the default access tokens for a given user name.
* @param userName is the user name or identifier.
* @return the default response tokens, presuming that the connect method
* fails.
*/
@Override
public AuthorizationResponse getDefaultAuthorizationResponse(String userName) {
return RESPONSE_UNREACHABLE;
}
private static void handleIOException(IOException e)
throws ManifoldCFException {
if (!(e instanceof java.net.SocketTimeoutException)
&& (e instanceof InterruptedIOException)) {
throw new ManifoldCFException("Interrupted: " + e.getMessage(), e,
ManifoldCFException.INTERRUPTED);
}
Logging.authorityConnectors.warn(
"JIRA: IO exception: " + e.getMessage(), e);
throw new ManifoldCFException("IO exception: " + e.getMessage(), e);
}
private static void handleResponseException(ResponseException e)
throws ManifoldCFException {
throw new ManifoldCFException("Response exception: " + e.getMessage(),
e);
}
protected static class GrantsThread extends Thread {
protected Throwable exception = null;
protected boolean result = false;
protected AmazonS3 amazonS3 = null;
private HashMap<String, Set<Grant>> grants;
public GrantsThread(AmazonS3 amazonS3) {
super();
this.amazonS3 = amazonS3;
setDaemon(true);
grants = new HashMap<String, Set<Grant>>();
}
public void finishUp() throws InterruptedException, IOException,
ResponseException {
join();
Throwable thr = exception;
if (thr != null) {
if (thr instanceof IOException) {
throw (IOException) thr;
}
else if (thr instanceof ResponseException) {
throw (ResponseException) thr;
}
else if (thr instanceof RuntimeException) {
throw (RuntimeException) thr;
}
else {
throw (Error) thr;
}
}
}
@Override
public void run() {
List<Bucket> listBuckets = amazonS3.listBuckets();
for (Bucket bucket : listBuckets) {
AccessControlList bucketAcl = amazonS3.getBucketAcl(bucket
.getName());
if (bucketAcl != null)
grants.put(bucket.getName(), bucketAcl.getGrants());
}
}
public HashMap<String, Set<Grant>> getResult() {
return grants;
}
}
protected static class CheckThread extends Thread {
protected String result = "Unknown";
protected AmazonS3 s3 = null;
protected Throwable exception = null;
public CheckThread(AmazonS3 s3) {
this.s3 = s3;
}
public String getResult() {
return result;
}
public Throwable getException() {
return exception;
}
@Override
public void run() {
try {
if (s3 != null) {
Owner s3AccountOwner = s3.getS3AccountOwner();
if (s3AccountOwner != null) {
result = StringUtils.isNotEmpty(s3AccountOwner
.getDisplayName()) ? "Connection OK"
: "Connection Failed";
}
}
}
catch (AmazonServiceException e) {
result = "Connection Failed : " + e.getMessage();
exception = e;
Logging.authorityConnectors.error(e);
}
catch (AmazonClientException e) {
result = "Connection Failed : " + e.getMessage();
exception = e;
Logging.authorityConnectors.error(e);
}
}
}
}