Package com.atlassian.labs.speakeasy.proxy

Source Code of com.atlassian.labs.speakeasy.proxy.ProxyService$ProxyResponseHandler

package com.atlassian.labs.speakeasy.proxy;

import com.atlassian.applinks.api.*;
import com.atlassian.applinks.api.application.bamboo.BambooApplicationType;
import com.atlassian.applinks.api.application.confluence.ConfluenceApplicationType;
import com.atlassian.applinks.api.application.fecru.FishEyeCrucibleApplicationType;
import com.atlassian.applinks.api.application.jira.JiraApplicationType;
import com.atlassian.applinks.api.application.refapp.RefAppApplicationType;
import com.atlassian.labs.speakeasy.external.SpeakeasyService;
import com.atlassian.labs.speakeasy.external.UnauthorizedAccessException;
import com.atlassian.labs.speakeasy.manager.PermissionManager;
import com.atlassian.labs.speakeasy.model.Permission;
import com.atlassian.labs.speakeasy.util.JsonObjectMapper;
import com.atlassian.sal.api.net.Request;
import com.atlassian.sal.api.net.Response;
import com.atlassian.sal.api.net.ResponseException;
import com.atlassian.sal.api.net.ResponseHandler;
import com.atlassian.sal.api.user.UserManager;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;

/**
* Proxies requests to an application link
*/
@Component
public class ProxyService
{
    private static final String APP_TYPE = "appType";
    private static final String APP_ID = "appId";
    private static final String FORMAT_ERRORS = "formatErrors";
    private static final String PATH = "path";
    private final static Set<String> RESERVED_PARAMS = ImmutableSet.of(PATH, APP_ID, APP_TYPE, FORMAT_ERRORS);
    private final static Map<String,Class<? extends ApplicationType>> APPLINKS_TYPE_ALIASES = ImmutableMap.<String,Class<? extends ApplicationType>>builder().
            put("jira", JiraApplicationType.class).
            put("confluence", ConfluenceApplicationType.class).
            put("fecru", FishEyeCrucibleApplicationType.class).
            put("fisheye", FishEyeCrucibleApplicationType.class).
            put("crucible", FishEyeCrucibleApplicationType.class).
            put("bamboo", BambooApplicationType.class).
            put("refapp", RefAppApplicationType.class).
            build();
    private final ApplicationLinkService appLinkService;
    private final SpeakeasyService speakeasyService;
    private final UserManager userManager;
    private final PermissionManager permissionManager;

    @Autowired
    public ProxyService(ApplicationLinkService appLinkService, SpeakeasyService speakeasyService, UserManager userManager, PermissionManager permissionManager)
    {
        this.appLinkService = appLinkService;
        this.speakeasyService = speakeasyService;
        this.userManager = userManager;
        this.permissionManager = permissionManager;
    }

    @SuppressWarnings("unchecked")
    public int proxy(final HttpServletRequest req, final HttpServletResponse resp, final Request.MethodType methodType) throws UnauthorizedAccessException, IOException
    {
        String user = userManager.getRemoteUsername(req);
        if (!speakeasyService.canAccessSpeakeasy(user))
        {
            throw new UnauthorizedAccessException(user, "Must be able to access Speakeasy to proxy requests");
        }
        if (!permissionManager.allowsPermission(Permission.APPLINKS_PROXY))
        {
            throw new UnauthorizedAccessException(user, "Permission to use Application Links proxy not enabled on this instance");
        }

        try
        {
            return doProxy(req, resp, methodType);
        }
        catch (IOException e)
        {
            resp.sendError(400, "Exception during proxy: " + e.getMessage());
            return 400;
        }
    }

    private int doProxy(HttpServletRequest req, final HttpServletResponse resp, Request.MethodType methodType) throws IOException
    {
        String url = req.getParameter(PATH);
        String finalQueryString = buildProxyQueryString(req);
        String finalPath = buildUrlPath(methodType, url, finalQueryString, resp);
        if (finalPath == null)
        {
            return 400;
        }

        String appId = req.getParameter(APP_ID);
        String appType = req.getParameter(APP_TYPE);
        ApplicationLink appLink = getApplicationLink(resp, appId, appType);
        try
        {
            final ApplicationLinkRequestFactory requestFactory = appLink.createAuthenticatedRequestFactory();
            Request request = prepareRequest(req, methodType, finalPath, requestFactory);
            request.execute(new ProxyResponseHandler(resp));
        }
        catch(ResponseException re)
        {
            final String finalUrl = appLink.getRpcUrl() + finalPath;
            return handleProxyingException(finalUrl, req, resp, re);
        }
        catch (CredentialsRequiredException e)
        {
            return oauthChallenge(appLink, resp, e);
        }
        return 200;
    }

    private String buildUrlPath(Request.MethodType methodType, String url, String queryString, HttpServletResponse resp) throws IOException
    {
        if (url == null)
        {
            resp.sendError(400, "Target url not specified via 'path' query parameter");
            return null;
        }
        if (methodType == Request.MethodType.GET && queryString .length() > 0)
        {
            url = url + (url.contains("?") ? '&' : '?') + queryString;
        }
        return url;
    }

    private String buildProxyQueryString(HttpServletRequest req)
    {
        String queryString = "";
        Map<String,String[]> parameters = req.getParameterMap();
        for (String name : parameters.keySet())
        {
            if (RESERVED_PARAMS.contains(name))
            {
                continue;
            }

            Object val = parameters.get(name);
            if (val instanceof String[])
            {
                String[] params = (String[])val;
                for (String param : params)
                {
                    queryString = queryString + (queryString.length() > 0 ? "&" : "") + encode(name) + "=" + encode(param);;
                }
            }
            else
            {
                queryString = queryString + (queryString.length() > 0 ? "&" : "") + encode(name) + "=" + encode(req.getParameter(name));
            }

        }
        return queryString;
    }

    private ApplicationLink getApplicationLink(HttpServletResponse resp, String appId, String appType) throws IOException
    {
        if (appType == null && appId == null)
        {
            resp.sendError(400, "You must specify an appId or appType request parameter");
        }
        ApplicationLink appLink = null;
        if (appId != null)
        {
            try
            {
                appLink = getApplicationLinkByIdOrName(appId);
                if (appLink == null)
                {
                    resp.sendError(404, "No Application Link found for the id " + appId);
                }
            }
            catch (TypeNotInstalledException e)
            {
                resp.sendError(404, "No Application Link found for the id " + appId);
            }
        }
        else if (appType != null)
        {
            try
            {
                appLink = getPrimaryAppLinkByType(appType);
                if (appLink == null)
                {
                    resp.sendError(404, "No Application Link found for the type " + appType);
                }
            }
            catch (ClassNotFoundException e)
            {
                resp.sendError(404, "Application Link type not found " + appType);
            }
        }
        else
        {
            resp.sendError(400, "Application Link type 'appType' or id 'appId' not specified as a query parameter");
        }
        return appLink;
    }

    private String encode(String value)
    {
        try
        {
            return URLEncoder.encode(value, "UTF-8");
        }
        catch (UnsupportedEncodingException ex)
        {
            // should never happen
            throw new RuntimeException(ex);
        }
    }

    private int oauthChallenge(ApplicationLink appLink, HttpServletResponse resp, CredentialsRequiredException e) throws IOException
    {
        resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        final String authUri = e.getAuthorisationURI().toString();
        resp.setHeader("WWW-Authenticate", "OAuth realm=\"" + authUri + "\"");
        JsonObjectMapper.write(new OAuthAuthenticateResponse(appLink, authUri), resp.getWriter());
        return HttpServletResponse.SC_UNAUTHORIZED;
    }

    private int handleProxyingException(String finalUrl, HttpServletRequest req, HttpServletResponse resp, Exception e) throws IOException
    {
        final boolean format = Boolean.parseBoolean(req.getParameter(FORMAT_ERRORS));

        String errorMsg = "There was an error proxying your request to " + finalUrl + " because of " + e.getMessage();
        if (format)
        {
            formatError(resp, errorMsg);
        }
        else
        {
            resp.sendError(504, errorMsg);
            return 504;
        }
        return 400;
    }

    private void formatError(HttpServletResponse resp, String errorMsg)
            throws IOException
    {
        PrintWriter writer = resp.getWriter();
        writer.write("<h4>" + errorMsg+ "</h4>");
        writer.flush();
    }

    private Request prepareRequest(HttpServletRequest req,
            Request.MethodType methodType, String url,
            final ApplicationLinkRequestFactory requestFactory)
            throws CredentialsRequiredException, IOException
    {
        Request request = requestFactory.createRequest(methodType, url);
        // remove xsrf token check on the destination. Assumes this servlet requires an xsrf token
        request.setHeader("X-Atlassian-Token", "no-check");
        // forward the original ip or pass on already forwarded ip so logging is accurate.
        String xForward = req.getHeader("X-Forwarded-For");
        request.setHeader("X-Forwarded-For", xForward != null ? xForward : req.getRemoteAddr());

        if (methodType == Request.MethodType.POST)
        {

           String ctHeader = req.getHeader("Content-Type");
           if (ctHeader != null)
           {
               request.setHeader("Content-Type", ctHeader);
           }

           if (ctHeader != null && ctHeader.contains("application/x-www-form-urlencoded"))
           {
               List<String> params = new ArrayList<String>();
               final Map<String, String[]> parameterMap = (Map<String, String[]>) req.getParameterMap();
               for (String name : parameterMap.keySet())
               {
                   if (RESERVED_PARAMS.contains(name))
                   {
                       continue;
                   }
                   params.add(name);
                   params.add(req.getParameter(name));
               }
               request.addRequestParameters((String[]) params.toArray(new String[params.size()]));
           }
           else
           {
               String enc = req.getCharacterEncoding();
               String str = IOUtils.toString(req.getInputStream(), (enc == null ? "ISO8859_1" : enc));
               request.setRequestBody(str);
           }
        }
        return request;
    }

    @SuppressWarnings("unchecked")
    private ApplicationLink getPrimaryAppLinkByType(String type) throws ClassNotFoundException
    {

        Class<? extends ApplicationType> clazz = APPLINKS_TYPE_ALIASES.get(type.toLowerCase(Locale.US));
        if (clazz == null)
        {
            clazz = (Class<? extends ApplicationType>) getClass().getClassLoader().loadClass(type);
        }
        return appLinkService.getPrimaryApplicationLink(clazz);
    }

    private ApplicationLink getApplicationLinkByIdOrName(String id) throws TypeNotInstalledException
    {
        ApplicationId appId = null;
        try
        {
            appId = new ApplicationId(id);
        }
        catch (IllegalArgumentException ex)
        {
            // not a valid id, try for name;
        }

        for (ApplicationLink link : appLinkService.getApplicationLinks())
        {
            if ((appId == null && link.getName().equals(id)) ||
                 (appId != null && link.getId().equals(appId)))
            {
                return link;
            }
        }
        return null;
    }

    private class ProxyResponseHandler implements ResponseHandler<Response>
    {
        private final HttpServletResponse resp;

        public ProxyResponseHandler(HttpServletResponse resp)
        {
            this.resp = resp;
        }

        public void handle(Response response) throws ResponseException
        {
            if (response.isSuccessful())
            {
                InputStream responseStream = response.getResponseBodyAsStream();
                Map<String, String> headers = response.getHeaders();
                for (String key : headers.keySet())
                {
                    // don't pass on cookies set by linked application.
                    if (key.equalsIgnoreCase("Set-Cookie"))
                    {
                        continue;
                    }
                    resp.setHeader(key, headers.get(key));
                }
                try
                {
                    if (responseStream != null)
                    {
                        ServletOutputStream outputStream = resp.getOutputStream();
                        IOUtils.copy(responseStream, outputStream);
                        outputStream.flush();
                        outputStream.close();
                    }
                }
                catch (IOException e)
                {
                    throw new RuntimeException(e);
                }
            }
            else
            {
                try
                {
                    formatError(resp, "Request failed, check your configuration.");
                }
                catch (IOException e)
                {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}
TOP

Related Classes of com.atlassian.labs.speakeasy.proxy.ProxyService$ProxyResponseHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.