001/*
002 * Copyright (c) 2011-2017 Nexmo Inc
003 *
004 * Permission is hereby granted, free of charge, to any person obtaining a copy
005 * of this software and associated documentation files (the "Software"), to deal
006 * in the Software without restriction, including without limitation the rights
007 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
008 * copies of the Software, and to permit persons to whom the Software is
009 * furnished to do so, subject to the following conditions:
010 *
011 * The above copyright notice and this permission notice shall be included in
012 * all copies or substantial portions of the Software.
013 *
014 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
015 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
016 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
017 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
018 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
019 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
020 * THE SOFTWARE.
021 */
022package com.nexmo.client;
023
024
025import com.nexmo.client.account.AccountClient;
026import com.nexmo.client.application.ApplicationClient;
027import com.nexmo.client.auth.*;
028import com.nexmo.client.conversion.ConversionClient;
029import com.nexmo.client.insight.InsightClient;
030import com.nexmo.client.numbers.NumbersClient;
031import com.nexmo.client.redact.RedactClient;
032import com.nexmo.client.sms.SmsClient;
033import com.nexmo.client.sns.SnsClient;
034import com.nexmo.client.verify.VerifyClient;
035import com.nexmo.client.voice.VoiceClient;
036import org.apache.http.client.HttpClient;
037
038import java.io.IOException;
039import java.nio.file.Files;
040import java.nio.file.Path;
041import java.nio.file.Paths;
042
043/**
044 * Top-level Nexmo API client object.
045 * <p>
046 * Construct an instance of this object with one or more {@link AuthMethod}s (providing all the authentication methods
047 * for the APIs you wish to use), and then call {@link #getVoiceClient()} to obtain a client for the Nexmo Voice API.
048 * <p>
049 * Currently this object only constructs and provides access to {@link VoiceClient}. In the future it will manage
050 * clients for all of the Nexmo APIs.
051 */
052public class NexmoClient {
053    private AccountClient account;
054    private ApplicationClient application;
055    private InsightClient insight;
056    private NumbersClient numbers;
057    private SmsClient sms;
058    private VoiceClient voice;
059    private VerifyClient verify;
060    private SnsClient sns;
061    private ConversionClient conversion;
062    private RedactClient redact;
063    private HttpWrapper httpWrapper;
064
065    private NexmoClient(Builder builder) {
066        this.httpWrapper = new HttpWrapper(builder.httpConfig, builder.authCollection);
067        this.httpWrapper.setHttpClient(builder.httpClient);
068
069        this.account = new AccountClient(this.httpWrapper);
070        this.application = new ApplicationClient(this.httpWrapper);
071        this.insight = new InsightClient(this.httpWrapper);
072        this.numbers = new NumbersClient(this.httpWrapper);
073        this.verify = new VerifyClient(this.httpWrapper);
074        this.voice = new VoiceClient(this.httpWrapper);
075        this.sms = new SmsClient(this.httpWrapper);
076        this.sns = new SnsClient(this.httpWrapper);
077        this.conversion = new ConversionClient(this.httpWrapper);
078        this.redact = new RedactClient(this.httpWrapper);
079    }
080
081    public AccountClient getAccountClient() {
082        return this.account;
083    }
084
085    public ApplicationClient getApplicationClient() {
086        return this.application;
087    }
088
089    public InsightClient getInsightClient() {
090        return this.insight;
091    }
092
093    public NumbersClient getNumbersClient() {
094        return this.numbers;
095    }
096
097    public SmsClient getSmsClient() {
098        return this.sms;
099    }
100
101    public SnsClient getSnsClient() {
102        return this.sns;
103    }
104
105    public VerifyClient getVerifyClient() {
106        return this.verify;
107    }
108
109    public VoiceClient getVoiceClient() {
110        return this.voice;
111    }
112
113    public ConversionClient getConversionClient() {
114        return this.conversion;
115    }
116
117    public RedactClient getRedactClient() {
118        return this.redact;
119    }
120
121    /**
122     * Generate a JWT for the application the client has been configured with.
123     *
124     * @return A String containing the token data.
125     *
126     * @throws NexmoUnacceptableAuthException if no {@link JWTAuthMethod} is available
127     */
128    public String generateJwt() throws NexmoUnacceptableAuthException {
129        JWTAuthMethod authMethod = this.httpWrapper.getAuthCollection().getAuth(JWTAuthMethod.class);
130        return authMethod.generateToken();
131    }
132
133    /**
134     * @return The {@link HttpWrapper}
135     */
136    HttpWrapper getHttpWrapper() {
137        return httpWrapper;
138    }
139
140    public static Builder builder() {
141        return new Builder();
142    }
143
144    public static class Builder {
145        private AuthCollection authCollection;
146        private HttpConfig httpConfig = HttpConfig.defaultConfig();
147        private HttpClient httpClient;
148        private String applicationId;
149        private String apiKey;
150        private String apiSecret;
151        private String signatureSecret;
152        private byte[] privateKeyContents;
153
154        /**
155         * @param httpConfig Configuration options for the {@link HttpWrapper}
156         *
157         * @return The {@link Builder} to keep building.
158         */
159        public Builder httpConfig(HttpConfig httpConfig) {
160            this.httpConfig = httpConfig;
161            return this;
162        }
163
164        /**
165         * @param httpClient Custom implementation of {@link HttpClient}.
166         *
167         * @return The {@link Builder} to keep building.
168         */
169        public Builder httpClient(HttpClient httpClient) {
170            this.httpClient = httpClient;
171            return this;
172        }
173
174        /**
175         * When setting an applicationId, it is also expected that the {@link #privateKeyContents} will also be set.
176         *
177         * @param applicationId Used to identify each application.
178         *
179         * @return The {@link Builder} to keep building.
180         */
181        public Builder applicationId(String applicationId) {
182            this.applicationId = applicationId;
183            return this;
184        }
185
186        /**
187         * When setting an apiKey, it is also expected that {@link #apiSecret(String)} and/or {@link
188         * #signatureSecret(String)} will also be set.
189         *
190         * @param apiKey The API Key found in the dashboard for your account.
191         *
192         * @return The {@link Builder} to keep building.
193         */
194        public Builder apiKey(String apiKey) {
195            this.apiKey = apiKey;
196            return this;
197        }
198
199        /**
200         * When setting an apiSecret, it is also expected that {@link #apiKey(String)} will also be set.
201         *
202         * @param apiSecret The API Secret found in the dashboard for your account.
203         *
204         * @return The {@link Builder} to keep building.
205         */
206        public Builder apiSecret(String apiSecret) {
207            this.apiSecret = apiSecret;
208            return this;
209        }
210
211        /**
212         * When setting a signatureSecret, it is also expected that {@link #apiKey(String)} will also be set.
213         *
214         * @param signatureSecret The Signature Secret found in the dashboard for your account.
215         *
216         * @return The {@link Builder} to keep building.
217         */
218        public Builder signatureSecret(String signatureSecret) {
219            this.signatureSecret = signatureSecret;
220            return this;
221        }
222
223        /**
224         * When setting the contents of your private key, it is also expected that {@link #applicationId(String)} will
225         * also be set.
226         *
227         * @param privateKeyContents The contents of your private key used for JWT generation.
228         *
229         * @return The {@link Builder} to keep building.
230         */
231        public Builder privateKeyContents(byte[] privateKeyContents) {
232            this.privateKeyContents = privateKeyContents;
233            return this;
234        }
235
236        /**
237         * When setting the contents of your private key, it is also expected that {@link #applicationId(String)} will
238         * also be set.
239         *
240         * @param privateKeyContents The contents of your private key used for JWT generation.
241         *
242         * @return The {@link Builder} to keep building.
243         */
244        public Builder privateKeyContents(String privateKeyContents) {
245            return privateKeyContents(privateKeyContents.getBytes());
246        }
247
248        /**
249         * When setting the path of your private key, it is also expected that {@link #applicationId(String)} will also
250         * be set.
251         *
252         * @param privateKeyPath The path to your private key used for JWT generation.
253         *
254         * @return The {@link Builder} to keep building.
255         *
256         * @throws NexmoUnableToReadPrivateKeyException if the private key could not be read from the file system.
257         */
258        public Builder privateKeyPath(Path privateKeyPath) throws NexmoUnableToReadPrivateKeyException {
259            try {
260                return privateKeyContents(Files.readAllBytes(privateKeyPath));
261            } catch (IOException e) {
262                throw new NexmoUnableToReadPrivateKeyException("Unable to read private key at " + privateKeyPath, e);
263            }
264        }
265
266        /**
267         * When setting the path of your private key, it is also expected that {@link #applicationId(String)} will also
268         * be set.
269         *
270         * @param privateKeyPath The path to your private key used for JWT generation.
271         *
272         * @return The {@link Builder} to keep building.
273         *
274         * @throws NexmoUnableToReadPrivateKeyException if the private key could not be read from the file system.
275         */
276        public Builder privateKeyPath(String privateKeyPath) throws NexmoUnableToReadPrivateKeyException {
277            return privateKeyPath(Paths.get(privateKeyPath));
278        }
279
280        /**
281         * @return a new {@link NexmoClient} from the stored builder options.
282         *
283         * @throws NexmoClientCreationException if credentials aren't provided in a valid pairing or there were issues
284         *                                      generating an {@link JWTAuthMethod} with the provided credentials.
285         */
286        public NexmoClient build() {
287            this.authCollection = generateAuthCollection(this.applicationId,
288                    this.apiKey,
289                    this.apiSecret,
290                    this.signatureSecret,
291                    this.privateKeyContents
292            );
293            return new NexmoClient(this);
294        }
295
296        private AuthCollection generateAuthCollection(String applicationId, String key, String secret, String signature, byte[] privateKeyContents) {
297            AuthCollection authMethods = new AuthCollection();
298
299            try {
300                validateAuthParameters(applicationId, key, secret, signature, privateKeyContents);
301            } catch (IllegalStateException e) {
302                throw new NexmoClientCreationException("Failed to generate authentication methods.", e);
303            }
304
305            if (key != null && secret != null) {
306                authMethods.add(new TokenAuthMethod(key, secret));
307            }
308
309            if (key != null && signature != null) {
310                authMethods.add(new SignatureAuthMethod(key, signature));
311            }
312
313            if (applicationId != null && privateKeyContents != null) {
314                authMethods.add(new JWTAuthMethod(applicationId, privateKeyContents));
315            }
316
317            return authMethods;
318        }
319
320        private void validateAuthParameters(String applicationId, String key, String secret, String signature, byte[] privateKeyContents) {
321            if (key != null && secret == null && signature == null) {
322                throw new IllegalStateException(
323                        "You must provide an API secret or signature secret in addition to your API key.");
324            }
325
326            if (secret != null && key == null) {
327                throw new IllegalStateException("You must provide an API key in addition to your API secret.");
328            }
329
330            if (signature != null && key == null) {
331                throw new IllegalStateException("You must provide an API key in addition to your signature secret.");
332            }
333
334            if (applicationId == null && privateKeyContents != null) {
335                throw new IllegalStateException("You must provide an application ID in addition to your private key.");
336            }
337
338            if (applicationId != null && privateKeyContents == null) {
339                throw new IllegalStateException("You must provide a private key in addition to your application id.");
340            }
341        }
342    }
343}