blob: c815777f719eae0f2643e8ca7918034afa5ddb6e [file] [log] [blame]
/* $Id$ */
/**
* 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.jira;
import org.apache.manifoldcf.core.common.*;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Date;
import java.util.Set;
import java.util.Iterator;
import org.apache.manifoldcf.authorities.system.Logging;
import org.apache.manifoldcf.authorities.authorities.BaseAuthorityConnector;
import org.apache.manifoldcf.authorities.interfaces.AuthorizationResponse;
import org.apache.manifoldcf.core.interfaces.ConfigParams;
import org.apache.manifoldcf.core.interfaces.ManifoldCFException;
import org.apache.commons.lang.StringUtils;
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 java.util.Map.Entry;
/** Jira Authority Connector. This connector verifies user existence against Jira.
*/
public class JiraAuthorityConnector extends BaseAuthorityConnector {
/** Deny access token for default authority */
private final static String defaultAuthorityDenyToken = "DEAD_AUTHORITY";
// Configuration tabs
private static final String JIRA_SERVER_TAB_PROPERTY = "JiraAuthorityConnector.Server";
// Template names for configuration
/**
* Forward to the javascript to check the configuration parameters
*/
private static final String EDIT_CONFIG_HEADER_FORWARD = "editConfiguration_jira_server.js";
/**
* Server tab template
*/
private static final String EDIT_CONFIG_FORWARD_SERVER = "editConfiguration_jira_server.html";
/**
* Forward to the HTML template to view the configuration parameters
*/
private static final String VIEW_CONFIG_FORWARD = "viewConfiguration_jira.html";
// Session data
protected JiraSession session = null;
protected long lastSessionFetch = -1L;
protected static final long timeToRelease = 300000L;
// Parameter data
protected String jiraprotocol = null;
protected String jirahost = null;
protected String jiraport = null;
protected String jirapath = null;
protected String clientid = null;
protected String clientsecret = null;
public JiraAuthorityConnector() {
super();
}
/**
* Close the connection. Call this before discarding the connection.
*/
@Override
public void disconnect() throws ManifoldCFException {
if (session != null) {
session.close();
session = null;
lastSessionFetch = -1L;
}
jiraprotocol = null;
jirahost = null;
jiraport = null;
jirapath = null;
clientid = null;
clientsecret = null;
}
/**
* This method create a new JIRA session for a JIRA
* repository, if the repositoryId is not provided in the configuration, the
* connector will retrieve all the repositories exposed for this endpoint
* the it will start to use the first one.
*
* @param configParameters is the set of configuration parameters, which in
* this case describe the target appliance, basic auth configuration, etc.
* (This formerly came out of the ini file.)
*/
@Override
public void connect(ConfigParams configParams) {
super.connect(configParams);
jiraprotocol = params.getParameter(JiraConfig.JIRA_PROTOCOL_PARAM);
jirahost = params.getParameter(JiraConfig.JIRA_HOST_PARAM);
jiraport = params.getParameter(JiraConfig.JIRA_PORT_PARAM);
jirapath = params.getParameter(JiraConfig.JIRA_PATH_PARAM);
clientid = params.getParameter(JiraConfig.CLIENT_ID_PARAM);
clientsecret = params.getObfuscatedParameter(JiraConfig.CLIENT_SECRET_PARAM);
}
/**
* 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 {
try {
checkConnection();
return super.check();
} catch (ManifoldCFException e) {
return "Connection failed: " + e.getMessage();
}
}
/**
* Set up a session
*/
protected JiraSession getSession() throws ManifoldCFException {
if (session == null) {
// Check for parameter validity
if (StringUtils.isEmpty(jiraprotocol)) {
throw new ManifoldCFException("Parameter " + JiraConfig.JIRA_PROTOCOL_PARAM
+ " required but not set");
}
if (Logging.authorityConnectors.isDebugEnabled()) {
Logging.authorityConnectors.debug("JIRA: jiraprotocol = '" + jiraprotocol + "'");
}
if (StringUtils.isEmpty(jirahost)) {
throw new ManifoldCFException("Parameter " + JiraConfig.JIRA_HOST_PARAM
+ " required but not set");
}
if (Logging.authorityConnectors.isDebugEnabled()) {
Logging.authorityConnectors.debug("JIRA: jirahost = '" + jirahost + "'");
}
if (Logging.authorityConnectors.isDebugEnabled()) {
Logging.authorityConnectors.debug("JIRA: jiraport = '" + jiraport + "'");
}
if (StringUtils.isEmpty(jirapath)) {
throw new ManifoldCFException("Parameter " + JiraConfig.JIRA_PATH_PARAM
+ " required but not set");
}
if (Logging.authorityConnectors.isDebugEnabled()) {
Logging.authorityConnectors.debug("JIRA: jirapath = '" + jirapath + "'");
}
if (Logging.authorityConnectors.isDebugEnabled()) {
Logging.authorityConnectors.debug("JIRA: Clientid = '" + clientid + "'");
}
if (Logging.authorityConnectors.isDebugEnabled()) {
Logging.authorityConnectors.debug("JIRA: Clientsecret = '" + clientsecret + "'");
}
String jiraurl = jiraprotocol + "://" + jirahost + (StringUtils.isEmpty(jiraport)?"":":"+jiraport) + jirapath;
session = new JiraSession(clientid, clientsecret, jiraurl);
}
lastSessionFetch = System.currentTimeMillis();
return session;
}
@Override
public void poll() throws ManifoldCFException {
if (lastSessionFetch == -1L) {
return;
}
long currentTime = System.currentTimeMillis();
if (currentTime >= lastSessionFetch + timeToRelease) {
session.close();
session = null;
lastSessionFetch = -1L;
}
}
/**
* Fill in a Server tab configuration parameter map for calling a Velocity
* template.
*
* @param newMap is the map to fill in
* @param parameters is the current set of configuration parameters
*/
private static void fillInServerConfigurationMap(Map<String, Object> newMap, IPasswordMapperActivity mapper, ConfigParams parameters) {
String jiraprotocol = parameters.getParameter(JiraConfig.JIRA_PROTOCOL_PARAM);
String jirahost = parameters.getParameter(JiraConfig.JIRA_HOST_PARAM);
String jiraport = parameters.getParameter(JiraConfig.JIRA_PORT_PARAM);
String jirapath = parameters.getParameter(JiraConfig.JIRA_PATH_PARAM);
String clientid = parameters.getParameter(JiraConfig.CLIENT_ID_PARAM);
String clientsecret = parameters.getObfuscatedParameter(JiraConfig.CLIENT_SECRET_PARAM);
if (jiraprotocol == null)
jiraprotocol = JiraConfig.JIRA_PROTOCOL_DEFAULT;
if (jirahost == null)
jirahost = JiraConfig.JIRA_HOST_DEFAULT;
if (jiraport == null)
jiraport = JiraConfig.JIRA_PORT_DEFAULT;
if (jirapath == null)
jirapath = JiraConfig.JIRA_PATH_DEFAULT;
if (clientid == null)
clientid = JiraConfig.CLIENT_ID_DEFAULT;
if (clientsecret == null)
clientsecret = JiraConfig.CLIENT_SECRET_DEFAULT;
else
clientsecret = mapper.mapPasswordToKey(clientsecret);
newMap.put("JIRAPROTOCOL", jiraprotocol);
newMap.put("JIRAHOST", jirahost);
newMap.put("JIRAPORT", jiraport);
newMap.put("JIRAPATH", jirapath);
newMap.put("CLIENTID", clientid);
newMap.put("CLIENTSECRET", clientsecret);
}
/**
* 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.
*
* @param threadContext is the local thread context.
* @param out is the output to which any HTML should be sent.
* @param parameters are the configuration parameters, as they currently
* exist, for this connection being configured.
*/
@Override
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);
Messages.outputResourceWithVelocity(out,locale,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.
*
* @param threadContext is the local thread context.
* @param out is the output to which any HTML should be sent.
* @param parameters are the configuration parameters, as they currently
* exist, for this connection being configured.
* @param tabsArray is an array of tab names. Add to this array any tab
* names that are specific to the connector.
*/
@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, JIRA_SERVER_TAB_PROPERTY));
// Map the parameters
Map<String, Object> paramMap = new HashMap<String, Object>();
// Fill in the parameters from each tab
fillInServerConfigurationMap(paramMap, out, parameters);
// Output the Javascript - only one Velocity template for all tabs
Messages.outputResourceWithVelocity(out,locale,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("TabName", tabName);
// Server tab
// Fill in the parameters
fillInServerConfigurationMap(paramMap, out, parameters);
Messages.outputResourceWithVelocity(out,locale,EDIT_CONFIG_FORWARD_SERVER,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".
*
* @param threadContext is the local thread context.
* @param variableContext is the set of variables available from the post,
* including binary file post information.
* @param parameters are the configuration parameters, as they currently
* exist, for this connection being configured.
* @return null if all is well, or a string error message if there is an
* error that should prevent saving of the connection (and cause a
* redirection to an error page).
*
*/
@Override
public String processConfigurationPost(IThreadContext threadContext,
IPostParameters variableContext, ConfigParams parameters)
throws ManifoldCFException {
String jiraprotocol = variableContext.getParameter("jiraprotocol");
if (jiraprotocol != null)
parameters.setParameter(JiraConfig.JIRA_PROTOCOL_PARAM, jiraprotocol);
String jirahost = variableContext.getParameter("jirahost");
if (jirahost != null)
parameters.setParameter(JiraConfig.JIRA_HOST_PARAM, jirahost);
String jiraport = variableContext.getParameter("jiraport");
if (jiraport != null)
parameters.setParameter(JiraConfig.JIRA_PORT_PARAM, jiraport);
String jirapath = variableContext.getParameter("jirapath");
if (jirapath != null)
parameters.setParameter(JiraConfig.JIRA_PATH_PARAM, jirapath);
String clientid = variableContext.getParameter("clientid");
if (clientid != null)
parameters.setParameter(JiraConfig.CLIENT_ID_PARAM, clientid);
String clientsecret = variableContext.getParameter("clientsecret");
if (clientsecret != null)
parameters.setObfuscatedParameter(JiraConfig.CLIENT_SECRET_PARAM, variableContext.mapKeyToPassword(clientsecret));
return null;
}
/** Obtain the access tokens for a given Active Directory user name.
*@param userName is the user name or identifier.
*@return the response tokens (according to the current authority).
* (Should throws an exception only when a condition cannot be properly described within the authorization response object.)
*/
@Override
public AuthorizationResponse getAuthorizationResponse(String userName)
throws ManifoldCFException {
if (checkUserExists(userName))
return new AuthorizationResponse(new String[]{userName},AuthorizationResponse.RESPONSE_OK);
return RESPONSE_USERNOTFOUND;
}
/** 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);
}
// Background threads
protected static class CheckUserExistsThread extends Thread {
protected final JiraSession session;
protected final String userName;
protected Throwable exception = null;
protected boolean result = false;
public CheckUserExistsThread(JiraSession session, String userName) {
super();
this.session = session;
this.userName = userName;
setDaemon(true);
}
public void run() {
try {
result = session.checkUserExists(userName);
} catch (Throwable e) {
this.exception = e;
}
}
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;
}
}
}
public boolean getResult() {
return result;
}
}
protected boolean checkUserExists(String userName) throws ManifoldCFException {
CheckUserExistsThread t = new CheckUserExistsThread(getSession(), userName);
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 false;
}
protected static class CheckConnectionThread extends Thread {
protected final JiraSession session;
protected Throwable exception = null;
public CheckConnectionThread(JiraSession session) {
super();
this.session = session;
setDaemon(true);
}
public void run() {
try {
session.getRepositoryInfo();
} catch (Throwable e) {
this.exception = e;
}
}
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;
}
}
}
}
protected void checkConnection() throws ManifoldCFException {
CheckConnectionThread t = new CheckConnectionThread(getSession());
try {
t.start();
t.finishUp();
return;
} 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);
}
}
}