File Coverage

blib/lib/WWW/Domain/Registry/Joker.pm
Criterion Covered Total %
statement 21 118 17.8
branch 0 58 0.0
condition 0 27 0.0
subroutine 7 14 50.0
pod 7 7 100.0
total 35 224 15.6


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