File Coverage

blib/lib/WWW/Domain/Registry/Joker.pm
Criterion Covered Total %
statement 20 117 17.0
branch 0 58 0.0
condition 0 27 0.0
subroutine 7 14 50.0
pod 7 7 100.0
total 34 223 15.2


line stmt bran cond sub pod time code
1             package WWW::Domain::Registry::Joker;
2              
3 1     1   13203 use 5.006;
  1         2  
4 1     1   3 use strict;
  1         1  
  1         14  
5 1     1   2 use warnings;
  1         1  
  1         17  
6              
7 1     1   433 use LWP;
  1         31459  
  1         24  
8 1     1   4 use URI::Escape;
  1         1  
  1         45  
9              
10 1     1   396 use WWW::Domain::Registry::Joker::Loggish;
  1         2  
  1         29  
11              
12 1     1   357 use WWW::Domain::Registry::Joker::Response;
  1         1  
  1         1018  
13              
14             our @ISA = qw(WWW::Domain::Registry::Joker::Loggish);
15              
16             our $VERSION = '0.11';
17              
18             =head1 NAME
19              
20             WWW::Domain::Registry::Joker - an interface to the Joker.com DMAPI
21              
22             =head1 SYNOPSIS
23              
24             use WWW::Domain::Registry::Joker;
25              
26             $reg = new WWW::Domain::Registry::Joker('username' => 'testuser',
27             'password' => 'secret', 'debug' => 1);
28              
29             @res = $reg->result_list();
30              
31             eval {
32             $procid = $reg->do_request('ns-create', 'Host' => 'a.ns.example.com',
33             'IP' => '192.168.13.1');
34             };
35             if ($@) {
36             warn("Joker request failed: $@\n");
37             }
38              
39             =head1 DESCRIPTION
40              
41             The C module provides a Perl interface to
42             the Domain Management API (DMAPI) used by the Joker.com DNS registrar.
43             It is designed to help Joker.com resellers in automating the domain
44             registration and all the other relevant actions.
45              
46             The recommended usage of the C class is
47             to create an object, initialize it with the Joker.com reseller's username
48             and password, and then use it to send all the DMAPI requests. This will
49             take care of caching both login credentials and network connections
50             (at least as far as C takes care of caching connections to the same
51             webserver).
52              
53             In most cases it is not necessary to invoke the C method
54             explicitly, since all the "real" action methods check for an authentication
55             token and invoke C if there is none yet.
56              
57             =head1 METHODS
58              
59             The C class defines the following methods:
60              
61             =over 4
62              
63             =item * new ( PARAMS )
64              
65             Create a new Joker.com interface object with the specified parameters:
66              
67             =over 4
68              
69             =item * username
70              
71             The Joker.com reseller authentication username.
72              
73             =item * password
74              
75             The Joker.com reseller authentication password.
76              
77             =item * debug
78              
79             The diagnostic output level, 0 for no diagnostic messages.
80              
81             =item * dmapi_url
82              
83             The URL to use for Joker.com Domain Management API (DMAPI) requests;
84             if not specified, the standard URL I
85             is used.
86              
87             =back
88              
89             =cut
90              
91             sub new($ %)
92             {
93 0     0 1   my ($proto, %param) = @_;
94 0   0       my $class = ref $proto || $proto;
95 0           my $self;
96              
97 0           $self = WWW::Domain::Registry::Joker::Loggish::new($proto,
98             'authsid' => undef,
99             'lwp' => undef,
100             'err' => undef,
101             'fake' => 0,
102             'password' => undef,
103             'dmapi_url' => 'https://dmapi.joker.com/request',
104             'username' => undef,
105             %param,
106             );
107 0           return $self;
108             }
109              
110             =item * lwp ( [OBJECT] )
111              
112             Get or set the C object used for sending the actual
113             requests to the Joker.com web API.
114              
115             This method should probably never interest any consumers of this class :)
116              
117             =cut
118              
119             sub lwp($ $)
120             {
121 0     0 1   my ($self, $obj) = @_;
122              
123 0 0         if (defined($obj)) {
    0          
124 0 0 0       if (index(ref($obj), '::') == -1 ||
125             !$obj->isa('LWP::UserAgent')) {
126 0           die("WWW::Domain::Registry::Joker->lwp() requires a ".
127             "LWP::UserAgent object, not '".ref($obj)."'\n");
128             }
129 0           $self->{'lwp'} = $obj;
130             } elsif (!defined($self->{'lwp'})) {
131 0           $obj = new LWP::UserAgent();
132 0           $self->{'lwp'} = $obj;
133             }
134 0           return $self->{'lwp'};
135             }
136              
137             =item * build_request ( REQUEST, PARAMS )
138              
139             Build a C object for submitting an actual request to
140             the Joker.com API.
141              
142             This method should probably never interest any consumers of this class :)
143              
144             =cut
145              
146             sub build_request($ $ %)
147             {
148 0     0 1   my ($self, $req, %param) = @_;
149 0           my ($s, $k, $v, $l, $i);
150              
151 0           $s = "$self->{'dmapi_url'}/$req";
152 0 0         if (%param) {
153             $s .= '?'.join '&', map
154 0           uri_escape($_).'='.uri_escape($param{$_}), keys %param;
155             }
156 0           return new HTTP::Request('GET' => $s);
157             }
158              
159             =item * login ()
160              
161             Send a DMAPI login authentication request and obtain the auth SID for
162             use in the follow-up actual requests. The I and I
163             member variables must be initialized.
164              
165             =cut
166              
167             sub login($)
168             {
169 0     0 1   my ($self) = @_;
170 0           my ($req, $hresp, $resp);
171              
172             die("No DMAPI credentials supplied")
173 0 0 0       unless defined($self->{'username'}) && defined($self->{'password'});
174             $req = $self->build_request('login', 'username' => $self->{'username'},
175 0           'password' => $self->{'password'});
176 0           $self->debug("=== DMAPI login request\n".$req->as_string()."\n===\n");
177 0           $hresp = $self->lwp()->request($req);
178             $resp = new WWW::Domain::Registry::Joker::Response(
179 0           'debug' => $self->{'debug'}, 'log' => $self->{'log'});
180 0           $resp->parse($hresp);
181 0 0         die("DMAPI login: $resp->{Error}\n") if defined($resp->{'Error'});
182 0 0         die("DMAPI login: $resp->{status}\n") unless $resp->{'success'};
183             die("DMAPI login error: $resp->{code} $resp->{msg}\n")
184 0 0 0       unless defined($resp->{'code'}) && $resp->{'code'} == 0;
185             die("DMAPI login parse error: no auth session ID\n")
186 0 0         unless defined($resp->{'Auth-Sid'});
187 0           $self->{'authsid'} = $resp->{'Auth-Sid'};
188 0           return $self->{'authsid'};
189             }
190              
191             =item * query_domain_list ( PATTERN )
192              
193             Return information about the domains registered by this reseller whose names
194             match the supplied pattern. Returns a hash indexed by domain name, each
195             element of which is a hash:
196              
197             =over 4
198              
199             =item * domain
200              
201             The domain name (yes, again :))
202              
203             =item * exp
204              
205             The expiration date of the domain registration.
206              
207             =back
208              
209             Invokes the C method if necessary.
210              
211             =cut
212              
213             sub query_domain_list($ $)
214             {
215 0     0 1   my ($self, $pattern) = @_;
216 0           my ($req, $hresp, $resp);
217 0           my (%res);
218              
219 0 0         if (!defined($self->{'authsid'})) {
220 0 0         return undef unless $self->login();
221             }
222             $req = $self->build_request('query-domain-list',
223 0           'Auth-Sid' => $self->{'authsid'},
224             'pattern' => $pattern);
225 0           $self->debug("=== DMAPI qdlist request\n".$req->as_string()."\n===\n");
226 0           $hresp = $self->lwp()->request($req);
227             $resp = new WWW::Domain::Registry::Joker::Response(
228 0           'debug' => $self->{'debug'}, 'log' => $self->{'log'});
229 0           $resp->parse($hresp);
230 0 0         die("DMAPI qdlist: $resp->{Error}\n") if defined($resp->{'Error'});
231 0 0         die("DMAPI qdlist: $resp->{status}\n") unless $resp->{'success'};
232             die("DMAPI qdlist error: $resp->{code} $resp->{msg}\n")
233 0 0 0       unless defined($resp->{'code'}) && $resp->{'code'} == 0;
234 0           foreach (@{$resp->{'data'}}) {
  0            
235 0 0         if (!/^(\S+)\s+(\S+)$/) {
236 0           $self->debug("- invalid format $_");
237 0           next;
238             }
239 0           $res{$1} = { 'domain' => $1, 'exp' => $2 };
240             }
241 0           return %res;
242             }
243              
244             =item * do_request ( REQUEST, PARAMS )
245              
246             Send a DMAPI request with the name specified in C and
247             parameters in the C hash. The request name string and
248             the parameters (required and optional) are as specified by
249             the DMAPI documentation
250              
251             Note that for object modification requests (those which type is
252             C or ends in C<-modify>) if a parameter is supplied
253             with the empty string as a value, the C method will send
254             the I<"!@!"> string instead, since the DMAPI considers empty values to mean
255             no change requested.
256              
257             Invokes the C method if necessary.
258              
259             =cut
260              
261             sub do_request($ $ %)
262             {
263 0     0 1   my ($self, $type, %data) = @_;
264 0           my ($req, $hresp, $resp);
265 0           my (%d);
266              
267 0 0         if (!defined($self->{'authsid'})) {
268 0 0         return undef unless $self->login();
269             }
270 0           foreach (keys %data) {
271 0 0 0       if (defined($data{$_}) && length($data{$_})) {
    0 0        
272 0           $d{$_} = $data{$_};
273             } elsif ($type eq 'domain-owner-change' ||
274             $type =~ /-modify$/) {
275 0           $d{$_} = '!@!';
276             }
277             }
278             $req = $self->build_request("$type",
279 0           'Auth-Sid' => $self->{'authsid'},
280             %d);
281 0           $self->debug("=== DMAPI $type request\n".$req->as_string()."\n===\n");
282 0           $hresp = $self->lwp()->request($req);
283             $resp = new WWW::Domain::Registry::Joker::Response(
284 0           'debug' => $self->{'debug'}, 'log' => $self->{'log'});
285 0           $resp->parse($hresp);
286 0 0         die("DMAPI $type: $resp->{Error}\n") if defined($resp->{'Error'});
287 0 0         die("DMAPI $type: $resp->{status}\n") unless $resp->{'success'};
288             die("DMAPI $type error: $resp->{code} $resp->{msg}\n")
289 0 0 0       unless defined($resp->{'code'}) && $resp->{'code'} == 0;
290             die("DMAPI $type - no processing ID returned!\n")
291 0 0         unless defined($resp->{'Proc-ID'});
292 0           return $resp->{'Proc-ID'};
293             }
294              
295             =item * result_list ()
296              
297             Obtain the list of processed requests from the Joker.com DMAPI and
298             the corresponding result status and object ID (where applicable).
299             Returns a hash indexed by DMAPI I values.
300              
301             Invokes the C method if necessary.
302              
303             =cut
304              
305             sub result_list($)
306             {
307 0     0 1   my ($self) = @_;
308 0           my ($req, $hresp, $resp);
309 0           my (@r);
310 0           my (%a, %res);
311              
312 0 0         if (!defined($self->{'authsid'})) {
313 0 0         return undef unless $self->login();
314             }
315             $req = $self->build_request('result-list',
316 0           'Auth-Sid' => $self->{'authsid'});
317 0           $self->debug("=== DMAPI rlist request\n".$req->as_string()."\n===\n");
318 0           $hresp = $self->lwp()->request($req);
319             $resp = new WWW::Domain::Registry::Joker::Response(
320 0           'debug' => $self->{'debug'}, 'log' => $self->{'log'});
321 0           $resp->parse($hresp);
322 0 0         die("DMAPI rlist: $resp->{Error}\n") if defined($resp->{'Error'});
323 0 0         die("DMAPI rlist: $resp->{status}\n") unless $resp->{'success'};
324             die("DMAPI rlist error: $resp->{code} $resp->{msg}\n")
325 0 0 0       unless defined($resp->{'code'}) && $resp->{'code'} == 0;
326 0           %res = ();
327 0           foreach (@{$resp->{'data'}}) {
  0            
328 0           @r = split /\s+/;
329 0 0         if ($#r != 6) {
330 0           $self->debug("Unrecognized result-list line: $_");
331 0           next;
332             }
333 0           @a{qw/tstamp svtrid procid reqtype reqobject status cltrid/} =
334             @r;
335 0           $self->debug("result $a{procid} $a{status} $a{reqobject}");
336 0           $res{$a{'procid'}} = { %a };
337             }
338 0           return %res;
339             }
340              
341             =back
342              
343             =head1 EXAMPLES
344              
345             Initialize a C object with your reseller's
346             username and password:
347              
348             $jreq = new WWW::Domain::Registry::Joker('username' => 'me@example.com',
349             'password' => 'somekindofsecret');
350              
351             Fetch the list of pending and processed requests and their status:
352              
353             %h = $jreq->result_list();
354             foreach (sort { $a->{'procid'} cmp $b->{'procid'} } values %h) {
355             print join("\t",
356             @{$_}{qw/tstamp svtrid procid reqtype reqobject status cltrid/}).
357             "\n";
358             }
359              
360             Register a new nameserver:
361              
362             eval {
363             $jreq->do_request('ns-create', 'Host' => 'a.ns.example.net',
364             'IP' => '192.168.13.7');
365             };
366             print STDERR "ns-create error: $@\n" if ($@);
367              
368             Maybe some more examples are needed here :)
369              
370             =head1 ERRORS
371              
372             All the user-invoked methods die on any Joker.com errors with
373             a suitable error message placed in $@.
374              
375             =head1 SEE ALSO
376              
377             I - the Joker.com DMAPI
378             documentation
379              
380             =head1 BUGS
381              
382             =over 4
383              
384             =item *
385              
386             Reorder the methods placing the user-serviceable ones first.
387              
388             =item *
389              
390             Move C to a separate distribution?
391              
392             =item *
393              
394             Better error handling; exceptions? Error.pm? Something completely
395             different? Croak?
396              
397             =item *
398              
399             Croak instead of die here and there.
400              
401             =back
402              
403             =head1 HISTORY
404              
405             The C class was written by Peter Pentchev
406             in 2007.
407              
408             =head1 AUTHOR
409              
410             Peter Pentchev, Eroam@ringlet.netE
411              
412             =head1 COPYRIGHT AND LICENSE
413              
414             Copyright (C) 2007 - 2009 by Peter Pentchev
415              
416             This library is free software; you can redistribute it and/or modify
417             it under the same terms as Perl itself, either Perl version 5.8.8 or,
418             at your option, any later version of Perl 5 you may have available.
419              
420             =cut
421              
422             1;