File Coverage

blib/lib/cPanel/APIClient.pm
Criterion Covered Total %
statement 46 54 85.1
branch 12 22 54.5
condition 2 6 33.3
subroutine 6 6 100.0
pod 1 1 100.0
total 67 89 75.2


line stmt bran cond sub pod time code
1             package cPanel::APIClient;
2              
3 12     12   1895381 use strict;
  12         113  
  12         348  
4 12     12   103 use warnings;
  12         28  
  12         8139  
5              
6             our $VERSION = '0.10';
7              
8             =encoding utf-8
9              
10             =head1 NAME
11              
12             cPanel::APIClient - L APIs, à la TIMTOWTDI!
13              
14             =head1 SYNOPSIS
15              
16             Create a L object
17             to call cPanel APIs:
18              
19             my $cpanel = cPanel::APIClient->create(
20             service => 'cpanel',
21             transport => [ 'CLISync' ],
22             );
23              
24             my $resp = $cpanel->call_uapi( 'Email', 'list_pops' );
25              
26             my $pops_ar = $resp->get_data();
27              
28             Create a L object
29             to call WHM APIs:
30              
31             my $whm = cPanel::APIClient->create(
32             service => 'whm',
33             transport => [ 'CLISync' ],
34             );
35              
36             my $resp = $whm->call_api1( 'listaccts' );
37              
38             my $accts_ar = $resp->get_data();
39              
40             =head1 DESCRIPTION
41              
42             cPanel & WHM exposes a number of ways to access its APIs: different transport
43             mechanisms, different authentication schemes, etc. This library provides
44             client logic with sufficient abstractions to accommodate most supported
45             access mechanisms via a unified interface.
46              
47             This library intends to supersede L as the preferred way
48             to access cPanel & WHM’s APIs from Perl. It can also serve as a model for
49             similar client libraries in other languages.
50              
51             =head1 FEATURES
52              
53             =over
54              
55             =item * Fully object-oriented.
56              
57             =item * Can use blocking or non-blocking I/O. Non-blocking I/O implementation
58             works with almost any modern Perl event loop interface.
59              
60             =item * Uses minimal dependencies: no L &c.
61              
62             =item * Extensively tested.
63              
64             =item * Can run in pure Perl.
65              
66             =back
67              
68             =head1 CHARACTER ENCODING
69              
70             cPanel & WHM’s API is character-set-agnostic. All text that you give to this
71             library should thus be encoded to binary, and all strings that you’ll receive
72             back will be binary.
73              
74             This means that if you character-decode your inputs—as L
75             recommends—then you’ll need to encode your strings back to bytes before
76             giving them to this module.
77              
78             Use of UTF-8 encoding is B recommended!
79              
80             =head1 FUNCTIONS
81              
82             =head2 $client = cPanel::APIClient->create( %OPTS )
83              
84             A factory function that creates a “client” object that your code can
85             use to call the APIs.
86              
87             %OPTS are:
88              
89             =over
90              
91             =item * C - Required. The service that exposes the API(s) to call.
92             This controls the class of the returned object. Recognized values are:
93              
94             =over
95              
96             =item * C - Function will return a L
97             instance.
98              
99             =item * C - Function will return a L
100             instance.
101              
102             =back
103              
104             =item * C - Required. An array reference that describes the
105             transport mechanism to use. The first member of this array names the mechanism;
106             remaining arguments are key-value pairs of attributes to give to the
107             mechanism class’s constructor.
108              
109             Currently supported mechanisms are:
110              
111             =over
112              
113             =item * L (C) -
114             Synchronous HTTP requests.
115              
116             =item * L (C) -
117             Synchronous local requests via cPanel & WHM’s command-line API tools.
118              
119             =item * L (C) -
120             Asynchronous HTTP requests via
121             L, which can use any event loop interface.
122             As of this writing it supports L, L, and L
123             out-of-the-box.
124              
125             =item * L (C) -
126             Asynchronous HTTP requests via L (pure Perl).
127              
128             =back
129              
130             Which of the above to use will depend on your needs. If your application
131             is local to the cPanel & WHM server you might find it easiest to use
132             C. For HTTP C offers the best flexibility
133             and (probably) speed, whereas C and C can run in
134             pure Perl (assuming you have L).
135              
136             There currently is no documentation for how to create a 3rd-party transport
137             mechanism (e.g., if you want to use a different HTTP library). Submissions
138             via pull request will be evaluated on a case-by-case basis.
139              
140             =item * C - Some transports require this; others don’t.
141             The recognized schemes are:
142              
143             =over
144              
145             =item * C & C - Authenticate with an API token
146              
147             =item * C & C - Authenticate with a password
148              
149             =item * C, C, & C - Authenticate with a
150             password and two-factor authentication (2FA) token.
151              
152             =item * C only - Implicit authentication, only usable for local
153             transports.
154              
155             =back
156              
157             =back
158              
159             Depending on the C given, this function returns an instance of
160             either L or
161             L.
162              
163             =cut
164              
165             my @_REQUIRED = ( 'service', 'transport' );
166              
167             sub create {
168              
169             # We don’t need the class, but we mandate arrow syntax rather
170             # than static because it seems more consistent with Perl programmers’
171             # expectations of what it looks like to call a function whose purpose
172             # is to create an object.
173 7     7 1 355318 shift;
174              
175 7         76 my (%opts) = @_;
176              
177 7         40 my @missing = grep { !defined $opts{$_} } @_REQUIRED;
  14         71  
178 7 50       64 die "Missing: @missing" if @missing;
179              
180 7         49 my $creds = delete $opts{'credentials'};
181              
182 7         41 my ( $svc, $transport ) = delete @opts{@_REQUIRED};
183              
184 7 50       56 if ( my @extra = sort keys %opts ) {
185 0         0 die "Extra: @extra";
186             }
187              
188 7         26 my $full_ns = "cPanel::APIClient::Service::$svc";
189 7         57 _require($full_ns);
190              
191 7   33     107 my $authn = $creds && _parse_creds($creds);
192              
193 7         33 $transport = _parse_transport( $transport, $authn, $svc );
194              
195 7         92 return $full_ns->new( $transport, $authn );
196             }
197              
198             sub _parse_transport {
199 7     7   20 my ( $transport, $authn, $service_name ) = @_;
200              
201 7 50       29 if ( 'ARRAY' ne ref $transport ) {
202 0         0 die "“transport” should be an ARRAY reference, not $transport!";
203             }
204              
205 7         42 my ( $module, @args ) = (
206             @$transport,
207             service_name => $service_name,
208             );
209              
210 7         22 $module = "cPanel::APIClient::Transport::$module";
211 7         21 _require($module);
212              
213 7 50 33     62 if ( $module->NEEDS_CREDENTIALS() && !$authn ) {
214 0         0 die "Transporter “$transport” requires credentials!";
215             }
216              
217 7         39 return $module->new( $authn, @args );
218             }
219              
220             sub _require {
221 21     21   61 my ($full_ns) = @_;
222              
223 21 50       1438 die if !eval "require $full_ns; 1";
224             }
225              
226             sub _parse_creds {
227 7     7   28 my ($creds_hr) = @_;
228              
229 7         55 my %creds_copy = %$creds_hr;
230              
231 7         33 my $username = delete $creds_copy{'username'};
232              
233 7 50       53 if ( !defined $username ) {
234 0         0 die "Credentials need “username”!";
235             }
236              
237 7         29 my ( @extras, $class );
238              
239 7 100       27 if ( exists $creds_copy{'api_token'} ) {
    50          
240 6         45 @extras = ('api_token');
241 6         25 $class = 'cPanel::APIClient::Authn::Token';
242             }
243             elsif ( exists $creds_copy{'password'} ) {
244 1         4 @extras = ('password');
245              
246 1 50       5 if ( exists $creds_copy{'tfa_token'} ) {
247 0         0 push @extras, 'tfa_token';
248 0         0 $class = 'cPanel::APIClient::Authn::Password2FA';
249             }
250             else {
251 1         3 $class = 'cPanel::APIClient::Authn::Password';
252             }
253             }
254             else {
255 0         0 $class = 'cPanel::APIClient::Authn::Username';
256             }
257              
258 7         36 for my $key (@extras) {
259 7         19 my $val = delete $creds_copy{$key};
260 7 50       28 if ( !defined $val ) {
261 0         0 die "Undefined “$key” is invalid!";
262             }
263             }
264              
265 7 50       47 die "Bad “credentials”!" if %creds_copy;
266              
267 7         26 _require($class);
268              
269 7         39 return $class->new( $username, @{$creds_hr}{@extras} );
  7         47  
270             }
271              
272             =head1 LICENSE
273              
274             Copyright 2020 cPanel, L. L. C. All rights reserved. L
275              
276             This is free software; you can redistribute it and/or modify it under the
277             same terms as Perl itself. See L.
278              
279             =cut
280              
281             1;