| /* $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); |
| } |
| } |
| |
| } |
| |