001package com.pusher.client.util;
002
003import com.pusher.client.AuthorizationFailureException;
004import com.pusher.client.Authorizer;
005import java.io.BufferedReader;
006import java.io.DataOutputStream;
007import java.io.IOException;
008import java.io.InputStream;
009import java.io.InputStreamReader;
010import java.net.HttpURLConnection;
011import java.net.MalformedURLException;
012import java.net.URL;
013import java.util.HashMap;
014import java.util.Map;
015import javax.net.ssl.HttpsURLConnection;
016
017/**
018 * Used to authenticate a {@link com.pusher.client.channel.PrivateChannel
019 * private} or {@link com.pusher.client.channel.PresenceChannel presence}
020 * channel subscription.
021 *
022 * <p>
023 * Makes an HTTP request to a defined HTTP endpoint. Expects an authentication
024 * token to be returned.
025 * </p>
026 *
027 * <p>
028 * For more information see the <a
029 * href="http://pusher.com/docs/authenticating_users">Authenticating Users
030 * documentation</a>.
031 */
032public class HttpAuthorizer implements Authorizer {
033
034    private final URL endPoint;
035    private Map<String, String> mHeaders = new HashMap<String, String>();
036    private ConnectionFactory mConnectionFactory = null;
037
038    /**
039     * Creates a new authorizer.
040     *
041     * @param endPoint
042     *            The endpoint to be called when authenticating.
043     */
044    public HttpAuthorizer(final String endPoint) {
045        try {
046            this.endPoint = new URL(endPoint);
047            this.mConnectionFactory = new UrlEncodedConnectionFactory();
048        }
049        catch (final MalformedURLException e) {
050            throw new IllegalArgumentException("Could not parse authentication end point into a valid URL", e);
051        }
052    }
053
054    /**
055     * Creates a new authorizer.
056     *
057     * @param endPoint The endpoint to be called when authenticating.
058     * @param connectionFactory a custom connection factory to be used for building the connection
059     */
060    public HttpAuthorizer(final String endPoint, final ConnectionFactory connectionFactory) {
061        try {
062            this.endPoint = new URL(endPoint);
063            this.mConnectionFactory = connectionFactory;
064        } catch (final MalformedURLException e) {
065            throw new IllegalArgumentException("Could not parse authentication end point into a valid URL", e);
066        }
067    }
068
069    /**
070     * Set additional headers to be sent as part of the request.
071     *
072     * @param headers A map of headers
073     */
074    public void setHeaders(final Map<String, String> headers) {
075        mHeaders = headers;
076    }
077
078    /**
079     * Identifies if the HTTP request will be sent over HTTPS.
080     * @return true if the endpoint protocol is 'https'
081     */
082    public Boolean isSSL() {
083        return endPoint.getProtocol().equals("https");
084    }
085
086    @Override
087    public String authorize(final String channelName, final String socketId) throws AuthorizationFailureException {
088        try {
089            mConnectionFactory.setChannelName(channelName);
090            mConnectionFactory.setSocketId(socketId);
091            String body = mConnectionFactory.getBody();
092
093            final HashMap<String, String> defaultHeaders = new HashMap<String, String>();
094            defaultHeaders.put("Content-Type", mConnectionFactory.getContentType());
095            defaultHeaders.put("charset", mConnectionFactory.getCharset());
096
097            HttpURLConnection connection;
098            if (isSSL()) {
099                connection = (HttpsURLConnection)endPoint.openConnection();
100            }
101            else {
102                connection = (HttpURLConnection)endPoint.openConnection();
103            }
104            connection.setDoOutput(true);
105            connection.setDoInput(true);
106            connection.setInstanceFollowRedirects(false);
107            connection.setRequestMethod("POST");
108
109            // Add in the user defined headers
110            defaultHeaders.putAll(mHeaders);
111            // Add in the Content-Length, so it can't be overwritten by mHeaders
112            defaultHeaders.put("Content-Length","" + Integer.toString(body.getBytes().length));
113            
114            for (final String headerName : defaultHeaders.keySet()) {
115                final String headerValue = defaultHeaders.get(headerName);
116                connection.setRequestProperty(headerName, headerValue);
117            }
118
119            connection.setUseCaches(false);
120
121            // Send request
122            final DataOutputStream wr = new DataOutputStream(connection.getOutputStream());
123            wr.writeBytes(body);
124            wr.flush();
125            wr.close();
126
127            // Read response
128            final InputStream is = connection.getInputStream();
129            final BufferedReader rd = new BufferedReader(new InputStreamReader(is));
130            String line;
131            final StringBuffer response = new StringBuffer();
132            while ((line = rd.readLine()) != null) {
133                response.append(line);
134            }
135            rd.close();
136
137            final int responseHttpStatus = connection.getResponseCode();
138            if (responseHttpStatus != 200 && responseHttpStatus != 201) {
139                throw new AuthorizationFailureException(response.toString());
140            }
141
142            return response.toString();
143
144        }
145        catch (final IOException e) {
146            throw new AuthorizationFailureException(e);
147        }
148    }
149}